diff options
Diffstat (limited to 'xpdf')
91 files changed, 17485 insertions, 8631 deletions
diff --git a/xpdf/AcroForm.cc b/xpdf/AcroForm.cc new file mode 100644 index 0000000..86e7037 --- /dev/null +++ b/xpdf/AcroForm.cc @@ -0,0 +1,1897 @@ +//======================================================================== +// +// AcroForm.cc +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <math.h> +#include "gmem.h" +#include "GString.h" +#include "GList.h" +#include "Error.h" +#include "Object.h" +#include "PDFDoc.h" +#include "TextString.h" +#include "Gfx.h" +#include "GfxFont.h" +#include "OptionalContent.h" +#include "Annot.h" +#include "Lexer.h" +#include "AcroForm.h" + +//------------------------------------------------------------------------ + +#define acroFormFlagReadOnly (1 << 0) // all +#define acroFormFlagRequired (1 << 1) // all +#define acroFormFlagNoExport (1 << 2) // all +#define acroFormFlagMultiline (1 << 12) // text +#define acroFormFlagPassword (1 << 13) // text +#define acroFormFlagNoToggleToOff (1 << 14) // button +#define acroFormFlagRadio (1 << 15) // button +#define acroFormFlagPushbutton (1 << 16) // button +#define acroFormFlagCombo (1 << 17) // choice +#define acroFormFlagEdit (1 << 18) // choice +#define acroFormFlagSort (1 << 19) // choice +#define acroFormFlagFileSelect (1 << 20) // text +#define acroFormFlagMultiSelect (1 << 21) // choice +#define acroFormFlagDoNotSpellCheck (1 << 22) // text, choice +#define acroFormFlagDoNotScroll (1 << 23) // text +#define acroFormFlagComb (1 << 24) // text +#define acroFormFlagRadiosInUnison (1 << 25) // button +#define acroFormFlagRichText (1 << 25) // text +#define acroFormFlagCommitOnSelChange (1 << 26) // choice + +#define acroFormQuadLeft 0 +#define acroFormQuadCenter 1 +#define acroFormQuadRight 2 + +#define annotFlagHidden 0x0002 +#define annotFlagPrint 0x0004 +#define annotFlagNoView 0x0020 + +// distance of Bezier control point from center for circle approximation +// = (4 * (sqrt(2) - 1) / 3) * r +#define bezierCircle 0.55228475 + +//------------------------------------------------------------------------ + +// map an annotation ref to a page number +class AcroFormAnnotPage { +public: + AcroFormAnnotPage(int annotNumA, int annotGenA, int pageNumA) + { annotNum = annotNumA; annotGen = annotGenA; pageNum = pageNumA; } + int annotNum; + int annotGen; + int pageNum; +}; + +//------------------------------------------------------------------------ +// AcroForm +//------------------------------------------------------------------------ + +AcroForm *AcroForm::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA) { + AcroForm *acroForm; + Object fieldsObj, obj1, obj2; + int i; + + acroForm = new AcroForm(docA, acroFormObjA); + + if (acroFormObjA->dictLookup("NeedAppearances", &obj1)->isBool()) { + acroForm->needAppearances = obj1.getBool(); + } + obj1.free(); + + acroForm->buildAnnotPageList(catalog); + + if (!acroFormObjA->dictLookup("Fields", &obj1)->isArray()) { + if (!obj1.isNull()) { + error(errSyntaxError, -1, "AcroForm Fields entry is wrong type"); + } + obj1.free(); + delete acroForm; + return NULL; + } + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGetNF(i, &obj2); + acroForm->scanField(&obj2); + obj2.free(); + } + obj1.free(); + + return acroForm; +} + +AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA): Form(docA) { + acroFormObjA->copy(&acroFormObj); + needAppearances = gFalse; + annotPages = new GList(); + fields = new GList(); +} + +AcroForm::~AcroForm() { + acroFormObj.free(); + deleteGList(annotPages, AcroFormAnnotPage); + deleteGList(fields, AcroFormField); +} + +void AcroForm::buildAnnotPageList(Catalog *catalog) { + Object annotsObj, annotObj; + int pageNum, i; + + for (pageNum = 1; pageNum <= catalog->getNumPages(); ++pageNum) { + if (catalog->getPage(pageNum)->getAnnots(&annotsObj)->isArray()) { + for (i = 0; i < annotsObj.arrayGetLength(); ++i) { + if (annotsObj.arrayGetNF(i, &annotObj)->isRef()) { + annotPages->append(new AcroFormAnnotPage(annotObj.getRefNum(), + annotObj.getRefGen(), + pageNum)); + } + annotObj.free(); + } + } + annotsObj.free(); + } + //~ sort the list +} + +int AcroForm::lookupAnnotPage(Object *annotRef) { + AcroFormAnnotPage *annotPage; + int num, gen, i; + + if (!annotRef->isRef()) { + return 0; + } + num = annotRef->getRefNum(); + gen = annotRef->getRefGen(); + //~ use bin search + for (i = 0; i < annotPages->getLength(); ++i) { + annotPage = (AcroFormAnnotPage *)annotPages->get(i); + if (annotPage->annotNum == num && annotPage->annotGen == gen) { + return annotPage->pageNum; + } + } + return 0; +} + +void AcroForm::scanField(Object *fieldRef) { + AcroFormField *field; + Object fieldObj, kidsObj, kidRef, kidObj, subtypeObj; + GBool isTerminal; + int i; + + fieldRef->fetch(doc->getXRef(), &fieldObj); + if (!fieldObj.isDict()) { + error(errSyntaxError, -1, "AcroForm field object is wrong type"); + fieldObj.free(); + return; + } + + // if this field has a Kids array, and all of the kids have a Parent + // reference (i.e., they're all form fields, not widget + // annotations), then this is a non-terminal field, and we need to + // scan the kids + isTerminal = gTrue; + if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) { + isTerminal = gFalse; + for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGet(i, &kidObj); + if (kidObj.isDict()) { + if (kidObj.dictLookup("Parent", &subtypeObj)->isNull()) { + isTerminal = gTrue; + } + subtypeObj.free(); + } + kidObj.free(); + } + if (!isTerminal) { + for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGetNF(i, &kidRef); + scanField(&kidRef); + kidRef.free(); + } + } + } + kidsObj.free(); + + if (isTerminal) { + if ((field = AcroFormField::load(this, fieldRef))) { + fields->append(field); + } + } + + fieldObj.free(); +} + +void AcroForm::draw(int pageNum, Gfx *gfx, GBool printing) { + int i; + + for (i = 0; i < fields->getLength(); ++i) { + ((AcroFormField *)fields->get(i))->draw(pageNum, gfx, printing); + } +} + +int AcroForm::getNumFields() { + return fields->getLength(); +} + +FormField *AcroForm::getField(int idx) { + return (AcroFormField *)fields->get(idx); +} + +//------------------------------------------------------------------------ +// AcroFormField +//------------------------------------------------------------------------ + +AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { + GString *typeStr; + TextString *nameA; + Guint flagsA; + GBool haveFlags; + Object fieldObjA, parentObj, parentObj2, obj1, obj2; + AcroFormFieldType typeA; + AcroFormField *field; + + fieldRefA->fetch(acroFormA->doc->getXRef(), &fieldObjA); + + //----- get field info + + if (fieldObjA.dictLookup("T", &obj1)->isString()) { + nameA = new TextString(obj1.getString()); + } else { + nameA = new TextString(); + } + obj1.free(); + + if (fieldObjA.dictLookup("FT", &obj1)->isName()) { + typeStr = new GString(obj1.getName()); + } else { + typeStr = NULL; + } + obj1.free(); + + if (fieldObjA.dictLookup("Ff", &obj1)->isInt()) { + flagsA = (Guint)obj1.getInt(); + haveFlags = gTrue; + } else { + flagsA = 0; + haveFlags = gFalse; + } + obj1.free(); + + //----- get info from parent non-terminal fields + + fieldObjA.dictLookup("Parent", &parentObj); + while (parentObj.isDict()) { + + if (parentObj.dictLookup("T", &obj1)->isString()) { + if (nameA->getLength()) { + nameA->insert(0, (Unicode)'.'); + } + nameA->insert(0, obj1.getString()); + } + obj1.free(); + + if (!typeStr) { + if (parentObj.dictLookup("FT", &obj1)->isName()) { + typeStr = new GString(obj1.getName()); + } + obj1.free(); + } + + if (!haveFlags) { + if (parentObj.dictLookup("Ff", &obj1)->isInt()) { + flagsA = (Guint)obj1.getInt(); + haveFlags = gTrue; + } + obj1.free(); + } + + parentObj.dictLookup("Parent", &parentObj2); + parentObj.free(); + parentObj = parentObj2; + } + parentObj.free(); + + if (!typeStr) { + error(errSyntaxError, -1, "Missing type in AcroForm field"); + goto err1; + } else if (!typeStr->cmp("Btn")) { + if (flagsA & acroFormFlagPushbutton) { + typeA = acroFormFieldPushbutton; + } else if (flagsA & acroFormFlagRadio) { + typeA = acroFormFieldRadioButton; + } else { + typeA = acroFormFieldCheckbox; + } + } else if (!typeStr->cmp("Tx")) { + if (flagsA & acroFormFlagFileSelect) { + typeA = acroFormFieldFileSelect; + } else if (flagsA & acroFormFlagMultiline) { + typeA = acroFormFieldMultilineText; + } else { + typeA = acroFormFieldText; + } + } else if (!typeStr->cmp("Ch")) { + if (flagsA & acroFormFlagCombo) { + typeA = acroFormFieldComboBox; + } else { + typeA = acroFormFieldListBox; + } + } else if (!typeStr->cmp("Sig")) { + typeA = acroFormFieldSignature; + } else { + error(errSyntaxError, -1, "Invalid type in AcroForm field"); + goto err1; + } + delete typeStr; + + field = new AcroFormField(acroFormA, fieldRefA, &fieldObjA, + typeA, nameA, flagsA); + fieldObjA.free(); + return field; + + err1: + delete typeStr; + delete nameA; + fieldObjA.free(); + return NULL; +} + +AcroFormField::AcroFormField(AcroForm *acroFormA, + Object *fieldRefA, Object *fieldObjA, + AcroFormFieldType typeA, TextString *nameA, + Guint flagsA) { + acroForm = acroFormA; + fieldRefA->copy(&fieldRef); + fieldObjA->copy(&fieldObj); + type = typeA; + name = nameA; + flags = flagsA; +} + +AcroFormField::~AcroFormField() { + fieldRef.free(); + fieldObj.free(); + delete name; +} + +const char *AcroFormField::getType() { + switch (type) { + case acroFormFieldPushbutton: return "PushButton"; + case acroFormFieldRadioButton: return "RadioButton"; + case acroFormFieldCheckbox: return "Checkbox"; + case acroFormFieldFileSelect: return "FileSelect"; + case acroFormFieldMultilineText: return "MultilineText"; + case acroFormFieldText: return "Text"; + case acroFormFieldComboBox: return "ComboBox"; + case acroFormFieldListBox: return "ListBox"; + case acroFormFieldSignature: return "Signature"; + default: return NULL; + } +} + +Unicode *AcroFormField::getName(int *length) { + Unicode *u, *ret; + int n; + + u = name->getUnicode(); + n = name->getLength(); + ret = (Unicode *)gmallocn(n, sizeof(Unicode)); + memcpy(ret, u, n * sizeof(Unicode)); + *length = n; + return ret; +} + +Unicode *AcroFormField::getValue(int *length) { + Object obj1; + Unicode *u; + char *s; + TextString *ts; + int n, i; + + fieldLookup("V", &obj1); + if (obj1.isName()) { + s = obj1.getName(); + n = (int)strlen(s); + u = (Unicode *)gmallocn(n, sizeof(Unicode)); + for (i = 0; i < n; ++i) { + u[i] = s[i] & 0xff; + } + *length = n; + return u; + } else if (obj1.isString()) { + ts = new TextString(obj1.getString()); + n = ts->getLength(); + u = (Unicode *)gmallocn(n, sizeof(Unicode)); + memcpy(u, ts->getUnicode(), n * sizeof(Unicode)); + *length = n; + delete ts; + return u; + } else { + return NULL; + } +} + +void AcroFormField::draw(int pageNum, Gfx *gfx, GBool printing) { + Object kidsObj, annotRef, annotObj; + int i; + + // find the annotation object(s) + if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) { + for (i = 0; i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGetNF(i, &annotRef); + annotRef.fetch(acroForm->doc->getXRef(), &annotObj); + drawAnnot(pageNum, gfx, printing, &annotRef, &annotObj); + annotObj.free(); + annotRef.free(); + } + } else { + drawAnnot(pageNum, gfx, printing, &fieldRef, &fieldObj); + } + kidsObj.free(); +} + +void AcroFormField::drawAnnot(int pageNum, Gfx *gfx, GBool printing, + Object *annotRef, Object *annotObj) { + Object obj1, obj2; + double xMin, yMin, xMax, yMax, t; + int annotFlags; + GBool oc; + + if (!annotObj->isDict()) { + return; + } + + //----- get the page number + + // the "P" (page) field in annotations is optional, so we can't + // depend on it here + if (acroForm->lookupAnnotPage(annotRef) != pageNum) { + return; + } + + //----- check annotation flags + + if (annotObj->dictLookup("F", &obj1)->isInt()) { + annotFlags = obj1.getInt(); + } else { + annotFlags = 0; + } + obj1.free(); + if ((annotFlags & annotFlagHidden) || + (printing && !(annotFlags & annotFlagPrint)) || + (!printing && (annotFlags & annotFlagNoView))) { + return; + } + + //----- check the optional content entry + + annotObj->dictLookupNF("OC", &obj1); + if (acroForm->doc->getOptionalContent()->evalOCObject(&obj1, &oc) && !oc) { + obj1.free(); + return; + } + obj1.free(); + + //----- get the bounding box + + if (annotObj->dictLookup("Rect", &obj1)->isArray() && + obj1.arrayGetLength() == 4) { + xMin = yMin = xMax = yMax = 0; + if (obj1.arrayGet(0, &obj2)->isNum()) { + xMin = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(1, &obj2)->isNum()) { + yMin = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(2, &obj2)->isNum()) { + xMax = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(3, &obj2)->isNum()) { + yMax = obj2.getNum(); + } + obj2.free(); + if (xMin > xMax) { + t = xMin; xMin = xMax; xMax = t; + } + if (yMin > yMax) { + t = yMin; yMin = yMax; yMax = t; + } + } else { + error(errSyntaxError, -1, "Bad bounding box for annotation"); + obj1.free(); + return; + } + obj1.free(); + + //----- draw it + + if (acroForm->needAppearances) { + drawNewAppearance(gfx, annotObj->getDict(), + xMin, yMin, xMax, yMax); + } else { + drawExistingAppearance(gfx, annotObj->getDict(), + xMin, yMin, xMax, yMax); + } +} + +// Draw the existing appearance stream for a single annotation +// attached to this field. +void AcroFormField::drawExistingAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax) { + Object apObj, asObj, appearance, obj1; + + //----- get the appearance stream + + if (annot->lookup("AP", &apObj)->isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict()) { + if (annot->lookup("AS", &asObj)->isName()) { + obj1.dictLookupNF(asObj.getName(), &appearance); + } else if (obj1.dictGetLength() == 1) { + obj1.dictGetValNF(0, &appearance); + } else { + obj1.dictLookupNF("Off", &appearance); + } + asObj.free(); + } else { + apObj.dictLookupNF("N", &appearance); + } + obj1.free(); + } + apObj.free(); + + //----- draw it + + if (!appearance.isNone()) { + gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax); + appearance.free(); + } +} + +// Regenerate the appearnce for this field, and draw it. +void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax) { + Object appearance, mkObj, ftObj, appearDict, drObj, apObj, asObj; + Object obj1, obj2, obj3; + Dict *mkDict; + MemStream *appearStream; + GfxFontDict *fontDict; + GBool hasCaption; + double dx, dy, r; + GString *caption, *da; + GString **text; + GBool *selection; + AnnotBorderType borderType; + double borderWidth; + double *borderDash; + GString *appearanceState; + int borderDashLength, rot, quadding, comb, nOptions, topIdx, i, j; + + appearBuf = new GString(); + + // get the appearance characteristics (MK) dictionary + if (annot->lookup("MK", &mkObj)->isDict()) { + mkDict = mkObj.getDict(); + } else { + mkDict = NULL; + } + + // draw the background + if (mkDict) { + if (mkDict->lookup("BG", &obj1)->isArray() && + obj1.arrayGetLength() > 0) { + setColor(obj1.getArray(), gTrue, 0); + appearBuf->appendf("0 0 {0:.4f} {1:.4f} re f\n", + xMax - xMin, yMax - yMin); + } + obj1.free(); + } + + // get the field type + fieldLookup("FT", &ftObj); + + // draw the border + borderType = annotBorderSolid; + borderWidth = 1; + borderDash = NULL; + borderDashLength = 0; + if (annot->lookup("BS", &obj1)->isDict()) { + if (obj1.dictLookup("S", &obj2)->isName()) { + if (obj2.isName("S")) { + borderType = annotBorderSolid; + } else if (obj2.isName("D")) { + borderType = annotBorderDashed; + } else if (obj2.isName("B")) { + borderType = annotBorderBeveled; + } else if (obj2.isName("I")) { + borderType = annotBorderInset; + } else if (obj2.isName("U")) { + borderType = annotBorderUnderlined; + } + } + obj2.free(); + if (obj1.dictLookup("W", &obj2)->isNum()) { + borderWidth = obj2.getNum(); + } + obj2.free(); + if (obj1.dictLookup("D", &obj2)->isArray()) { + borderDashLength = obj2.arrayGetLength(); + borderDash = (double *)gmallocn(borderDashLength, sizeof(double)); + for (i = 0; i < borderDashLength; ++i) { + if (obj2.arrayGet(i, &obj3)->isNum()) { + borderDash[i] = obj3.getNum(); + } else { + borderDash[i] = 1; + } + obj3.free(); + } + } + obj2.free(); + } else { + obj1.free(); + if (annot->lookup("Border", &obj1)->isArray()) { + if (obj1.arrayGetLength() >= 3) { + if (obj1.arrayGet(2, &obj2)->isNum()) { + borderWidth = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGetLength() >= 4) { + if (obj1.arrayGet(3, &obj2)->isArray()) { + borderType = annotBorderDashed; + borderDashLength = obj2.arrayGetLength(); + borderDash = (double *)gmallocn(borderDashLength, sizeof(double)); + for (i = 0; i < borderDashLength; ++i) { + if (obj2.arrayGet(i, &obj3)->isNum()) { + borderDash[i] = obj3.getNum(); + } else { + borderDash[i] = 1; + } + obj3.free(); + } + } else { + // Adobe draws no border at all if the last element is of + // the wrong type. + borderWidth = 0; + } + obj2.free(); + } + } + } + } + obj1.free(); + if (mkDict) { + if (borderWidth > 0) { + mkDict->lookup("BC", &obj1); + if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) { + mkDict->lookup("BG", &obj1); + } + if (obj1.isArray() && obj1.arrayGetLength() > 0) { + dx = xMax - xMin; + dy = yMax - yMin; + + // radio buttons with no caption have a round border + hasCaption = mkDict->lookup("CA", &obj2)->isString(); + obj2.free(); + if (ftObj.isName("Btn") && (flags & acroFormFlagRadio) && !hasCaption) { + r = 0.5 * (dx < dy ? dx : dy); + switch (borderType) { + case annotBorderDashed: + appearBuf->append("["); + for (i = 0; i < borderDashLength; ++i) { + appearBuf->appendf(" {0:.4f}", borderDash[i]); + } + appearBuf->append("] 0 d\n"); + // fall through to the solid case + case annotBorderSolid: + case annotBorderUnderlined: + appearBuf->appendf("{0:.4f} w\n", borderWidth); + setColor(obj1.getArray(), gFalse, 0); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * borderWidth, "s"); + break; + case annotBorderBeveled: + case annotBorderInset: + appearBuf->appendf("{0:.4f} w\n", 0.5 * borderWidth); + setColor(obj1.getArray(), gFalse, 0); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * borderWidth, "s"); + setColor(obj1.getArray(), gFalse, + borderType == annotBorderBeveled ? 1 : -1); + drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth); + setColor(obj1.getArray(), gFalse, + borderType == annotBorderBeveled ? -1 : 1); + drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth); + break; + } + + } else { + switch (borderType) { + case annotBorderDashed: + appearBuf->append("["); + for (i = 0; i < borderDashLength; ++i) { + appearBuf->appendf(" {0:.4f}", borderDash[i]); + } + appearBuf->append("] 0 d\n"); + // fall through to the solid case + case annotBorderSolid: + appearBuf->appendf("{0:.4f} w\n", borderWidth); + setColor(obj1.getArray(), gFalse, 0); + appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re s\n", + 0.5 * borderWidth, + dx - borderWidth, dy - borderWidth); + break; + case annotBorderBeveled: + case annotBorderInset: + setColor(obj1.getArray(), gTrue, + borderType == annotBorderBeveled ? 1 : -1); + appearBuf->append("0 0 m\n"); + appearBuf->appendf("0 {0:.4f} l\n", dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + dx - borderWidth, dy - borderWidth); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + borderWidth, dy - borderWidth); + appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth); + appearBuf->append("f\n"); + setColor(obj1.getArray(), gTrue, + borderType == annotBorderBeveled ? -1 : 1); + appearBuf->append("0 0 m\n"); + appearBuf->appendf("{0:.4f} 0 l\n", dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + dx - borderWidth, dy - borderWidth); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + dx - borderWidth, borderWidth); + appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth); + appearBuf->append("f\n"); + break; + case annotBorderUnderlined: + appearBuf->appendf("{0:.4f} w\n", borderWidth); + setColor(obj1.getArray(), gFalse, 0); + appearBuf->appendf("0 0 m {0:.4f} 0 l s\n", dx); + break; + } + + // clip to the inside of the border + appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re W n\n", + borderWidth, + dx - 2 * borderWidth, dy - 2 * borderWidth); + } + } + obj1.free(); + } + } + gfree(borderDash); + + // get the resource dictionary + fieldLookup("DR", &drObj); + + // build the font dictionary + if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) { + fontDict = new GfxFontDict(acroForm->doc->getXRef(), NULL, obj1.getDict()); + } else { + fontDict = NULL; + } + obj1.free(); + + // get the default appearance string + if (fieldLookup("DA", &obj1)->isString()) { + da = obj1.getString()->copy(); + } else { + da = NULL; + } + obj1.free(); + + // get the rotation value + rot = 0; + if (mkDict) { + if (mkDict->lookup("R", &obj1)->isInt()) { + rot = obj1.getInt(); + } + obj1.free(); + } + + // get the appearance state + annot->lookup("AP", &apObj); + annot->lookup("AS", &asObj); + appearanceState = NULL; + if (asObj.isName()) { + appearanceState = new GString(asObj.getName()); + } else if (apObj.isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict() && obj1.dictGetLength() == 1) { + appearanceState = new GString(obj1.dictGetKey(0)); + } + obj1.free(); + } + if (!appearanceState) { + appearanceState = new GString("Off"); + } + asObj.free(); + apObj.free(); + + // draw the field contents + if (ftObj.isName("Btn")) { + caption = NULL; + if (mkDict) { + if (mkDict->lookup("CA", &obj1)->isString()) { + caption = obj1.getString()->copy(); + } + obj1.free(); + } + // radio button + if (flags & acroFormFlagRadio) { + //~ Acrobat doesn't draw a caption if there is no AP dict (?) + if (fieldLookup("V", &obj1) + ->isName(appearanceState->getCString())) { + if (caption) { + drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, + gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth); + } else { + if (mkDict) { + if (mkDict->lookup("BC", &obj2)->isArray() && + obj2.arrayGetLength() > 0) { + dx = xMax - xMin; + dy = yMax - yMin; + setColor(obj2.getArray(), gTrue, 0); + drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), "f"); + } + obj2.free(); + } + } + } + obj1.free(); + // pushbutton + } else if (flags & acroFormFlagPushbutton) { + if (caption) { + drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, + gFalse, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth); + } + // checkbox + } else { + fieldLookup("V", &obj1); + if (obj1.isName() && !obj1.isName("Off")) { + if (!caption) { + caption = new GString("3"); // ZapfDingbats checkmark + } + drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, + gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth); + } + obj1.free(); + } + if (caption) { + delete caption; + } + } else if (ftObj.isName("Tx")) { + //~ value strings can be Unicode + if (!fieldLookup("V", &obj1)->isString()) { + obj1.free(); + fieldLookup("DV", &obj1); + } + if (obj1.isString()) { + if (fieldLookup("Q", &obj2)->isInt()) { + quadding = obj2.getInt(); + } else { + quadding = acroFormQuadLeft; + } + obj2.free(); + comb = 0; + if (flags & acroFormFlagComb) { + if (fieldLookup("MaxLen", &obj2)->isInt()) { + comb = obj2.getInt(); + } + obj2.free(); + } + drawText(obj1.getString(), da, fontDict, + flags & acroFormFlagMultiline, comb, quadding, + gTrue, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth); + } + obj1.free(); + } else if (ftObj.isName("Ch")) { + //~ value/option strings can be Unicode + if (fieldLookup("Q", &obj1)->isInt()) { + quadding = obj1.getInt(); + } else { + quadding = acroFormQuadLeft; + } + obj1.free(); + // combo box + if (flags & acroFormFlagCombo) { + if (fieldLookup("V", &obj1)->isString()) { + drawText(obj1.getString(), da, fontDict, + gFalse, 0, quadding, gTrue, gFalse, rot, + xMin, yMin, xMax, yMax, borderWidth); + //~ Acrobat draws a popup icon on the right side + } + obj1.free(); + // list box + } else { + if (fieldObj.dictLookup("Opt", &obj1)->isArray()) { + nOptions = obj1.arrayGetLength(); + // get the option text + text = (GString **)gmallocn(nOptions, sizeof(GString *)); + for (i = 0; i < nOptions; ++i) { + text[i] = NULL; + obj1.arrayGet(i, &obj2); + if (obj2.isString()) { + text[i] = obj2.getString()->copy(); + } else if (obj2.isArray() && obj2.arrayGetLength() == 2) { + if (obj2.arrayGet(1, &obj3)->isString()) { + text[i] = obj3.getString()->copy(); + } + obj3.free(); + } + obj2.free(); + if (!text[i]) { + text[i] = new GString(); + } + } + // get the selected option(s) + selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); + //~ need to use the I field in addition to the V field + fieldLookup("V", &obj2); + for (i = 0; i < nOptions; ++i) { + selection[i] = gFalse; + if (obj2.isString()) { + if (!obj2.getString()->cmp(text[i])) { + selection[i] = gTrue; + } + } else if (obj2.isArray()) { + for (j = 0; j < obj2.arrayGetLength(); ++j) { + if (obj2.arrayGet(j, &obj3)->isString() && + !obj3.getString()->cmp(text[i])) { + selection[i] = gTrue; + } + obj3.free(); + } + } + } + obj2.free(); + // get the top index + if (fieldObj.dictLookup("TI", &obj2)->isInt()) { + topIdx = obj2.getInt(); + } else { + topIdx = 0; + } + obj2.free(); + // draw the text + drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding, + xMin, yMin, xMax, yMax, borderWidth); + for (i = 0; i < nOptions; ++i) { + delete text[i]; + } + gfree(text); + gfree(selection); + } + obj1.free(); + } + } else if (ftObj.isName("Sig")) { + //~unimp + } else { + error(errSyntaxError, -1, "Unknown field type"); + } + + delete appearanceState; + if (da) { + delete da; + } + + // build the appearance stream dictionary + appearDict.initDict(acroForm->doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(acroForm->doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(xMax - xMin)); + obj1.arrayAdd(obj2.initReal(yMax - yMin)); + appearDict.dictAdd(copyString("BBox"), &obj1); + + // set the resource dictionary + if (drObj.isDict()) { + appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1)); + } + drObj.free(); + + // build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.initStream(appearStream); + + // draw it + gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax); + + appearance.free(); + delete appearBuf; + appearBuf = NULL; + if (fontDict) { + delete fontDict; + } + ftObj.free(); + mkObj.free(); +} + +// Set the current fill or stroke color, based on <a> (which should +// have 1, 3, or 4 elements). If <adjust> is +1, color is brightened; +// if <adjust> is -1, color is darkened; otherwise color is not +// modified. +void AcroFormField::setColor(Array *a, GBool fill, int adjust) { + Object obj1; + double color[4]; + int nComps, i; + + nComps = a->getLength(); + if (nComps > 4) { + nComps = 4; + } + for (i = 0; i < nComps && i < 4; ++i) { + if (a->get(i, &obj1)->isNum()) { + color[i] = obj1.getNum(); + } else { + color[i] = 0; + } + obj1.free(); + } + if (nComps == 4) { + adjust = -adjust; + } + if (adjust > 0) { + for (i = 0; i < nComps; ++i) { + color[i] = 0.5 * color[i] + 0.5; + } + } else if (adjust < 0) { + for (i = 0; i < nComps; ++i) { + color[i] = 0.5 * color[i]; + } + } + if (nComps == 4) { + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", + color[0], color[1], color[2], color[3], + fill ? 'k' : 'K'); + } else if (nComps == 3) { + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", + color[0], color[1], color[2], + fill ? "rg" : "RG"); + } else { + appearBuf->appendf("{0:.2f} {1:c}\n", + color[0], + fill ? 'g' : 'G'); + } +} + +// Draw the variable text or caption for a field. +void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, + GBool multiline, int comb, int quadding, + GBool txField, GBool forceZapfDingbats, int rot, + double xMin, double yMin, double xMax, double yMax, + double border) { + GString *text2; + GList *daToks; + GString *tok; + GfxFont *font; + double dx, dy; + double fontSize, fontSize2, x, xPrev, y, w, w2, wMax; + int tfPos, tmPos, i, j, k, c; + + //~ if there is no MK entry, this should use the existing content stream, + //~ and only replace the marked content portion of it + //~ (this is only relevant for Tx fields) + + // check for a Unicode string + //~ this currently drops all non-Latin1 characters + if (text->getLength() >= 2 && + text->getChar(0) == '\xfe' && text->getChar(1) == '\xff') { + text2 = new GString(); + for (i = 2; i+1 < text->getLength(); i += 2) { + c = ((text->getChar(i) & 0xff) << 8) + (text->getChar(i+1) & 0xff); + if (c <= 0xff) { + text2->append((char)c); + } else { + text2->append('?'); + } + } + } else { + text2 = text; + } + + // parse the default appearance string + tfPos = tmPos = -1; + if (da) { + daToks = new GList(); + i = 0; + while (i < da->getLength()) { + while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { + ++i; + } + if (i < da->getLength()) { + for (j = i + 1; + j < da->getLength() && !Lexer::isSpace(da->getChar(j)); + ++j) ; + daToks->append(new GString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // force ZapfDingbats + //~ this should create the font if needed (?) + if (forceZapfDingbats) { + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos); + if (tok->cmp("/ZaDb")) { + tok->clear(); + tok->append("/ZaDb"); + } + } + } + + // get the font and font size + font = NULL; + fontSize = 0; + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos); + if (tok->getLength() >= 1 && tok->getChar(0) == '/') { + if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { + error(errSyntaxError, -1, "Unknown font in field's DA string"); + } + } else { + error(errSyntaxError, -1, + "Invalid font name in 'Tf' operator in field's DA string"); + } + tok = (GString *)daToks->get(tfPos + 1); + fontSize = atof(tok->getCString()); + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + + // setup + if (txField) { + appearBuf->append("/Tx BMC\n"); + } + appearBuf->append("q\n"); + if (rot == 90) { + appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin); + dx = yMax - yMin; + dy = xMax - xMin; + } else if (rot == 180) { + appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", + xMax - xMin, yMax - yMin); + dx = xMax - yMax; + dy = yMax - yMin; + } else if (rot == 270) { + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin); + dx = yMax - yMin; + dy = xMax - xMin; + } else { // assume rot == 0 + dx = xMax - xMin; + dy = yMax - yMin; + } + appearBuf->append("BT\n"); + + // multi-line text + if (multiline) { + // note: the comb flag is ignored in multiline mode + + wMax = dx - 2 * border - 4; + + // compute font autosize + if (fontSize == 0) { + for (fontSize = 20; fontSize > 1; --fontSize) { + y = dy - 3; + w2 = 0; + i = 0; + while (i < text2->getLength()) { + getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); + if (w > w2) { + w2 = w; + } + i = k; + y -= fontSize; + } + // approximate the descender for the last line + if (y >= 0.33 * fontSize) { + break; + } + } + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.2f}", fontSize); + } + } + + // starting y coordinate + // (note: each line of text starts with a Td operator that moves + // down a line) + y = dy - 3; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->append('0'); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)daToks->get(i))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 0 {0:.4f} Tm\n", y); + } + + // write a series of lines of text + i = 0; + xPrev = 0; + while (i < text2->getLength()) { + + getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); + + // compute text start position + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = (dx - w) / 2; + break; + case acroFormQuadRight: + x = dx - border - 2 - w; + break; + } + + // draw the line + appearBuf->appendf("{0:.4f} {1:.4f} Td\n", x - xPrev, -fontSize); + appearBuf->append('('); + for (; i < j; ++i) { + c = text2->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + + // next line + i = k; + xPrev = x; + } + + // single-line text + } else { + //~ replace newlines with spaces? - what does Acrobat do? + + // comb formatting + if (comb > 0) { + + // compute comb spacing + w = (dx - 2 * border) / comb; + + // compute font autosize + if (fontSize == 0) { + fontSize = dy - 2 * border; + if (w < fontSize) { + fontSize = w; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // compute text start position + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = border + 2 + 0.5 * (comb - text2->getLength()) * w; + break; + case acroFormQuadRight: + x = border + 2 + (comb - text2->getLength()) * w; + break; + } + y = 0.5 * dy - 0.4 * fontSize; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)daToks->get(i))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + } + + // write the text string + //~ this should center (instead of left-justify) each character within + //~ its comb cell + for (i = 0; i < text2->getLength(); ++i) { + if (i > 0) { + appearBuf->appendf("{0:.4f} 0 Td\n", w); + } + appearBuf->append('('); + c = text2->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("{0:.4f} 0 Td\n", w); + } else { + appearBuf->append(c); + } + appearBuf->append(") Tj\n"); + } + + // regular (non-comb) formatting + } else { + + // compute string width + if (font && !font->isCIDFont()) { + w = 0; + for (i = 0; i < text2->getLength(); ++i) { + w += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i)); + } + } else { + // otherwise, make a crude estimate + w = text2->getLength() * 0.5; + } + + // compute font autosize + if (fontSize == 0) { + fontSize = dy - 2 * border; + fontSize2 = (dx - 4 - 2 * border) / w; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // compute text start position + w *= fontSize; + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = (dx - w) / 2; + break; + case acroFormQuadRight: + x = dx - border - 2 - w; + break; + } + y = 0.5 * dy - 0.4 * fontSize; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)daToks->get(i))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + } + + // write the text string + appearBuf->append('('); + for (i = 0; i < text2->getLength(); ++i) { + c = text2->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + } + } + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + if (txField) { + appearBuf->append("EMC\n"); + } + + if (daToks) { + deleteGList(daToks, GString); + } + if (text2 != text) { + delete text2; + } +} + +// Draw the variable text or caption for a field. +void AcroFormField::drawListBox(GString **text, GBool *selection, + int nOptions, int topIdx, + GString *da, GfxFontDict *fontDict, + GBool quadding, double xMin, double yMin, + double xMax, double yMax, double border) { + GList *daToks; + GString *tok; + GfxFont *font; + double fontSize, fontSize2, x, y, w, wMax; + int tfPos, tmPos, i, j, c; + + //~ if there is no MK entry, this should use the existing content stream, + //~ and only replace the marked content portion of it + //~ (this is only relevant for Tx fields) + + // parse the default appearance string + tfPos = tmPos = -1; + if (da) { + daToks = new GList(); + i = 0; + while (i < da->getLength()) { + while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { + ++i; + } + if (i < da->getLength()) { + for (j = i + 1; + j < da->getLength() && !Lexer::isSpace(da->getChar(j)); + ++j) ; + daToks->append(new GString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // get the font and font size + font = NULL; + fontSize = 0; + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos); + if (tok->getLength() >= 1 && tok->getChar(0) == '/') { + if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { + error(errSyntaxError, -1, "Unknown font in field's DA string"); + } + } else { + error(errSyntaxError, -1, + "Invalid font name in 'Tf' operator in field's DA string"); + } + tok = (GString *)daToks->get(tfPos + 1); + fontSize = atof(tok->getCString()); + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + + // compute font autosize + if (fontSize == 0) { + wMax = 0; + for (i = 0; i < nOptions; ++i) { + if (font && !font->isCIDFont()) { + w = 0; + for (j = 0; j < text[i]->getLength(); ++j) { + w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); + } + } else { + // otherwise, make a crude estimate + w = text[i]->getLength() * 0.5; + } + if (w > wMax) { + wMax = w; + } + } + fontSize = yMax - yMin - 2 * border; + fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // draw the text + y = yMax - yMin - 1.1 * fontSize; + for (i = topIdx; i < nOptions; ++i) { + + // setup + appearBuf->append("q\n"); + + // draw the background if selected + if (selection[i]) { + appearBuf->append("0 g f\n"); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", + border, + y - 0.2 * fontSize, + xMax - xMin - 2 * border, + 1.1 * fontSize); + } + + // setup + appearBuf->append("BT\n"); + + // compute string width + if (font && !font->isCIDFont()) { + w = 0; + for (j = 0; j < text[i]->getLength(); ++j) { + w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); + } + } else { + // otherwise, make a crude estimate + w = text[i]->getLength() * 0.5; + } + + // compute text start position + w *= fontSize; + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = (xMax - xMin - w) / 2; + break; + case acroFormQuadRight: + x = xMax - xMin - border - 2 - w; + break; + } + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (j = 0; j < daToks->getLength(); ++j) { + appearBuf->append((GString *)daToks->get(j))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + } + + // change the text color if selected + if (selection[i]) { + appearBuf->append("1 g\n"); + } + + // write the text string + appearBuf->append('('); + for (j = 0; j < text[i]->getLength(); ++j) { + c = text[i]->getChar(j) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + + // next line + y -= 1.1 * fontSize; + } + + if (daToks) { + deleteGList(daToks, GString); + } +} + +// Figure out how much text will fit on the next line. Returns: +// *end = one past the last character to be included +// *width = width of the characters start .. end-1 +// *next = index of first character on the following line +void AcroFormField::getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next) { + double w, dw; + int j, k, c; + + // figure out how much text will fit on the line + //~ what does Adobe do with tabs? + w = 0; + for (j = start; j < text->getLength() && w <= wMax; ++j) { + c = text->getChar(j) & 0xff; + if (c == 0x0a || c == 0x0d) { + break; + } + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + if (w > wMax) { + for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; + for (; k > start && text->getChar(k-1) == ' '; --k) ; + if (k > start) { + j = k; + } + if (j == start) { + // handle the pathological case where the first character is + // too wide to fit on the line all by itself + j = start + 1; + } + } + *end = j; + + // compute the width + w = 0; + for (k = start; k < j; ++k) { + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + *width = w; + + // next line + while (j < text->getLength() && text->getChar(j) == ' ') { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0d) { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0a) { + ++j; + } + *next = j; +} + +// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>). +// <cmd> is used to draw the circle ("f", "s", or "b"). +void AcroFormField::drawCircle(double cx, double cy, double r, + const char *cmd) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx + r, cy); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + r, cy + bezierCircle * r, + cx + bezierCircle * r, cy + r, + cx, cy + r); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - bezierCircle * r, cy + r, + cx - r, cy + bezierCircle * r, + cx - r, cy); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - r, cy - bezierCircle * r, + cx - bezierCircle * r, cy - r, + cx, cy - r); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + bezierCircle * r, cy - r, + cx + r, cy - bezierCircle * r, + cx + r, cy); + appearBuf->appendf("{0:s}\n", cmd); +} + +// Draw the top-left half of an (approximate) circle of radius <r> +// centered at (<cx>, <cy>). +void AcroFormField::drawCircleTopLeft(double cx, double cy, double r) { + double r2; + + r2 = r / sqrt(2.0); + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx + r2, cy + r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + (1 - bezierCircle) * r2, + cy + (1 + bezierCircle) * r2, + cx - (1 - bezierCircle) * r2, + cy + (1 + bezierCircle) * r2, + cx - r2, + cy + r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - (1 + bezierCircle) * r2, + cy + (1 - bezierCircle) * r2, + cx - (1 + bezierCircle) * r2, + cy - (1 - bezierCircle) * r2, + cx - r2, + cy - r2); + appearBuf->append("S\n"); +} + +// Draw the bottom-right half of an (approximate) circle of radius <r> +// centered at (<cx>, <cy>). +void AcroFormField::drawCircleBottomRight(double cx, double cy, double r) { + double r2; + + r2 = r / sqrt(2.0); + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx - r2, cy - r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - (1 - bezierCircle) * r2, + cy - (1 + bezierCircle) * r2, + cx + (1 - bezierCircle) * r2, + cy - (1 + bezierCircle) * r2, + cx + r2, + cy - r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + (1 + bezierCircle) * r2, + cy - (1 - bezierCircle) * r2, + cx + (1 + bezierCircle) * r2, + cy + (1 - bezierCircle) * r2, + cx + r2, + cy + r2); + appearBuf->append("S\n"); +} + +Object *AcroFormField::getResources(Object *res) { + Object kidsObj, annotObj, obj1; + int i; + + if (acroForm->needAppearances) { + fieldLookup("DR", res); + } else { + res->initArray(acroForm->doc->getXRef()); + // find the annotation object(s) + if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) { + for (i = 0; i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGet(i, &annotObj); + if (annotObj.isDict()) { + if (getAnnotResources(annotObj.getDict(), &obj1)->isDict()) { + res->arrayAdd(&obj1); + } else { + obj1.free(); + } + } + annotObj.free(); + } + } else { + if (getAnnotResources(fieldObj.getDict(), &obj1)->isDict()) { + res->arrayAdd(&obj1); + } else { + obj1.free(); + } + } + kidsObj.free(); + } + + return res; +} + +Object *AcroFormField::getAnnotResources(Dict *annot, Object *res) { + Object apObj, asObj, appearance, obj1; + + // get the appearance stream + if (annot->lookup("AP", &apObj)->isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict()) { + if (annot->lookup("AS", &asObj)->isName()) { + obj1.dictLookup(asObj.getName(), &appearance); + } else if (obj1.dictGetLength() == 1) { + obj1.dictGetVal(0, &appearance); + } else { + obj1.dictLookup("Off", &appearance); + } + asObj.free(); + } else { + obj1.copy(&appearance); + } + obj1.free(); + } + apObj.free(); + + if (appearance.isStream()) { + appearance.streamGetDict()->lookup("Resources", res); + } else { + res->initNull(); + } + appearance.free(); + + return res; +} + +// Look up an inheritable field dictionary entry. +Object *AcroFormField::fieldLookup(const char *key, Object *obj) { + return fieldLookup(fieldObj.getDict(), key, obj); +} + +Object *AcroFormField::fieldLookup(Dict *dict, const char *key, Object *obj) { + Object parent; + + if (!dict->lookup(key, obj)->isNull()) { + return obj; + } + obj->free(); + if (dict->lookup("Parent", &parent)->isDict()) { + fieldLookup(parent.getDict(), key, obj); + } else { + // some fields don't specify a parent, so we check the AcroForm + // dictionary just in case + acroForm->acroFormObj.dictLookup(key, obj); + } + parent.free(); + return obj; +} diff --git a/xpdf/AcroForm.h b/xpdf/AcroForm.h new file mode 100644 index 0000000..8e0075a --- /dev/null +++ b/xpdf/AcroForm.h @@ -0,0 +1,128 @@ +//======================================================================== +// +// AcroForm.h +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef ACROFORM_H +#define ACROFORM_H + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "Form.h" + +class TextString; +class GfxFont; +class GfxFontDict; + +//------------------------------------------------------------------------ + +class AcroForm: public Form { +public: + + static AcroForm *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA); + + virtual ~AcroForm(); + + virtual const char *getType() { return "AcroForm"; } + + virtual void draw(int pageNum, Gfx *gfx, GBool printing); + + virtual int getNumFields(); + virtual FormField *getField(int idx); + +private: + + AcroForm(PDFDoc *docA, Object *acroFormObjA); + void buildAnnotPageList(Catalog *catalog); + int lookupAnnotPage(Object *annotRef); + void scanField(Object *fieldRef); + + Object acroFormObj; + GBool needAppearances; + GList *annotPages; // [AcroFormAnnotPage] + GList *fields; // [AcroFormField] + + friend class AcroFormField; +}; + +//------------------------------------------------------------------------ + +enum AcroFormFieldType { + acroFormFieldPushbutton, + acroFormFieldRadioButton, + acroFormFieldCheckbox, + acroFormFieldFileSelect, + acroFormFieldMultilineText, + acroFormFieldText, + acroFormFieldComboBox, + acroFormFieldListBox, + acroFormFieldSignature +}; + +class AcroFormField: public FormField { +public: + + static AcroFormField *load(AcroForm *acroFormA, Object *fieldRefA); + + virtual ~AcroFormField(); + + virtual const char *getType(); + virtual Unicode *getName(int *length); + virtual Unicode *getValue(int *length); + + virtual Object *getResources(Object *res); + +private: + + AcroFormField(AcroForm *acroFormA, Object *fieldRefA, Object *fieldObjA, + AcroFormFieldType typeA, TextString *nameA, + Guint flagsA); + void draw(int pageNum, Gfx *gfx, GBool printing); + void drawAnnot(int pageNum, Gfx *gfx, GBool printing, + Object *annotRef, Object *annotObj); + void drawExistingAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax); + void drawNewAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax); + void setColor(Array *a, GBool fill, int adjust); + void drawText(GString *text, GString *da, GfxFontDict *fontDict, + GBool multiline, int comb, int quadding, + GBool txField, GBool forceZapfDingbats, int rot, + double xMin, double yMin, double xMax, double yMax, + double border); + void drawListBox(GString **text, GBool *selection, + int nOptions, int topIdx, + GString *da, GfxFontDict *fontDict, + GBool quadding, double xMin, double yMin, + double xMax, double yMax, double border); + void getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next); + void drawCircle(double cx, double cy, double r, const char *cmd); + void drawCircleTopLeft(double cx, double cy, double r); + void drawCircleBottomRight(double cx, double cy, double r); + Object *getAnnotResources(Dict *annot, Object *res); + Object *fieldLookup(const char *key, Object *obj); + Object *fieldLookup(Dict *dict, const char *key, Object *obj); + + AcroForm *acroForm; + Object fieldRef; + Object fieldObj; + AcroFormFieldType type; + TextString *name; + Guint flags; + GString *appearBuf; + + friend class AcroForm; +}; + +#endif diff --git a/xpdf/Annot.cc b/xpdf/Annot.cc index 887157f..ff1e376 100644 --- a/xpdf/Annot.cc +++ b/xpdf/Annot.cc @@ -24,56 +24,44 @@ #include "Lexer.h" #include "PDFDoc.h" #include "OptionalContent.h" +#include "Form.h" #include "Annot.h" +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + //------------------------------------------------------------------------ #define annotFlagHidden 0x0002 #define annotFlagPrint 0x0004 #define annotFlagNoView 0x0020 -#define fieldFlagReadOnly 0x00000001 -#define fieldFlagRequired 0x00000002 -#define fieldFlagNoExport 0x00000004 -#define fieldFlagMultiline 0x00001000 -#define fieldFlagPassword 0x00002000 -#define fieldFlagNoToggleToOff 0x00004000 -#define fieldFlagRadio 0x00008000 -#define fieldFlagPushbutton 0x00010000 -#define fieldFlagCombo 0x00020000 -#define fieldFlagEdit 0x00040000 -#define fieldFlagSort 0x00080000 -#define fieldFlagFileSelect 0x00100000 -#define fieldFlagMultiSelect 0x00200000 -#define fieldFlagDoNotSpellCheck 0x00400000 -#define fieldFlagDoNotScroll 0x00800000 -#define fieldFlagComb 0x01000000 -#define fieldFlagRichText 0x02000000 -#define fieldFlagRadiosInUnison 0x02000000 -#define fieldFlagCommitOnSelChange 0x04000000 - -#define fieldQuadLeft 0 -#define fieldQuadCenter 1 -#define fieldQuadRight 2 - // distance of Bezier control point from center for circle approximation // = (4 * (sqrt(2) - 1) / 3) * r #define bezierCircle 0.55228475 +#define lineEndSize1 6 +#define lineEndSize2 10 +#define lineArrowAngle (M_PI / 6) + //------------------------------------------------------------------------ // AnnotBorderStyle //------------------------------------------------------------------------ AnnotBorderStyle::AnnotBorderStyle(AnnotBorderType typeA, double widthA, double *dashA, int dashLengthA, - double rA, double gA, double bA) { + double *colorA, int nColorCompsA) { type = typeA; width = widthA; dash = dashA; dashLength = dashLengthA; - r = rA; - g = gA; - b = bA; + color[0] = colorA[0]; + color[1] = colorA[1]; + color[2] = colorA[2]; + color[3] = colorA[3]; + nColorComps = nColorCompsA; } AnnotBorderStyle::~AnnotBorderStyle() { @@ -92,7 +80,8 @@ Annot::Annot(PDFDoc *docA, Dict *dict, Ref *refA) { double borderWidth; double *borderDash; int borderDashLength; - double borderR, borderG, borderB; + double borderColor[4]; + int nBorderColorComps; double t; int i; @@ -160,9 +149,11 @@ Annot::Annot(PDFDoc *docA, Dict *dict, Ref *refA) { borderWidth = 1; borderDash = NULL; borderDashLength = 0; - borderR = 0; - borderG = 0; - borderB = 1; + nBorderColorComps = 3; + borderColor[0] = 0; + borderColor[1] = 0; + borderColor[2] = 1; + borderColor[3] = 0; if (dict->lookup("BS", &obj1)->isDict()) { if (obj1.dictLookup("S", &obj2)->isName()) { if (obj2.isName("S")) { @@ -223,28 +214,31 @@ Annot::Annot(PDFDoc *docA, Dict *dict, Ref *refA) { } obj2.free(); } + } else { + // an empty Border array also means "no border" + borderWidth = 0; } } } obj1.free(); - if (dict->lookup("C", &obj1)->isArray() && obj1.arrayGetLength() == 3) { - if (obj1.arrayGet(0, &obj2)->isNum()) { - borderR = obj2.getNum(); - } - obj1.free(); - if (obj1.arrayGet(1, &obj2)->isNum()) { - borderG = obj2.getNum(); - } - obj1.free(); - if (obj1.arrayGet(2, &obj2)->isNum()) { - borderB = obj2.getNum(); + if (dict->lookup("C", &obj1)->isArray() && + (obj1.arrayGetLength() == 1 || + obj1.arrayGetLength() == 3 || + obj1.arrayGetLength() == 4)) { + nBorderColorComps = obj1.arrayGetLength(); + for (i = 0; i < nBorderColorComps; ++i) { + if (obj1.arrayGet(i, &obj2)->isNum()) { + borderColor[i] = obj2.getNum(); + } else { + borderColor[i] = 0; + } + obj2.free(); } - obj1.free(); } obj1.free(); borderStyle = new AnnotBorderStyle(borderType, borderWidth, borderDash, borderDashLength, - borderR, borderG, borderB); + borderColor, nBorderColorComps); //----- get the appearance state @@ -304,350 +298,188 @@ Annot::~Annot() { ocObj.free(); } -void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) { - Object mkObj, ftObj, appearDict, drObj, obj1, obj2, obj3; - Dict *mkDict; - MemStream *appearStream; - GfxFontDict *fontDict; - GBool hasCaption; - double w, dx, dy, r; - double *dash; - GString *caption, *da; - GString **text; - GBool *selection; - int rot, dashLength, ff, quadding, comb, nOptions, topIdx, i, j; +void Annot::generateAnnotAppearance() { + Object obj; + + appearance.fetch(doc->getXRef(), &obj); + if (!obj.isStream()) { + if (type) { + if (!type->cmp("Line")) { + generateLineAppearance(); + } else if (!type->cmp("PolyLine")) { + generatePolyLineAppearance(); + } else if (!type->cmp("Polygon")) { + generatePolygonAppearance(); + } + } + } + obj.free(); +} - // must be a Widget annotation - if (type && type->cmp("Widget")) { +//~ this doesn't draw the caption +void Annot::generateLineAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1, x2, y2, dx, dy, len, w; + double lx1, ly1, lx2, ly2; + double tx1, ty1, tx2, ty2; + double ax1, ay1, ax2, ay2; + double bx1, by1, bx2, by2; + double leaderLen, leaderExtLen, leaderOffLen; + AnnotLineEndType lineEnd1, lineEnd2; + GBool fill; + + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); return; } appearBuf = new GString(); - // get the appearance characteristics (MK) dictionary - if (annot->lookup("MK", &mkObj)->isDict()) { - mkDict = mkObj.getDict(); - } else { - mkDict = NULL; + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); } + obj1.free(); - // draw the background - if (mkDict) { - if (mkDict->lookup("BG", &obj1)->isArray() && - obj1.arrayGetLength() > 0) { - setColor(obj1.getArray(), gTrue, 0); - appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n", - xMax - xMin, yMax - yMin); + //----- set line style, colors + setLineStyle(borderStyle, &w); + setStrokeColor(borderStyle->getColor(), borderStyle->getNumColorComps()); + fill = gFalse; + if (annotObj.dictLookup("IC", &obj1)->isArray()) { + if (setFillColor(&obj1)) { + fill = gTrue; } - obj1.free(); - } - - // get the field type - fieldLookup(field, acroForm, "FT", &ftObj); - - // get the field flags (Ff) value - if (fieldLookup(field, acroForm, "Ff", &obj1)->isInt()) { - ff = obj1.getInt(); - } else { - ff = 0; } obj1.free(); - // draw the border - if (mkDict) { - w = borderStyle->getWidth(); - if (w > 0) { - mkDict->lookup("BC", &obj1); - if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) { - mkDict->lookup("BG", &obj1); - } - if (obj1.isArray() && obj1.arrayGetLength() > 0) { - dx = xMax - xMin; - dy = yMax - yMin; - - // radio buttons with no caption have a round border - hasCaption = mkDict->lookup("CA", &obj2)->isString(); - obj2.free(); - if (ftObj.isName("Btn") && (ff & fieldFlagRadio) && !hasCaption) { - r = 0.5 * (dx < dy ? dx : dy); - switch (borderStyle->getType()) { - case annotBorderDashed: - appearBuf->append("["); - borderStyle->getDash(&dash, &dashLength); - for (i = 0; i < dashLength; ++i) { - appearBuf->appendf(" {0:.2f}", dash[i]); - } - appearBuf->append("] 0 d\n"); - // fall through to the solid case - case annotBorderSolid: - case annotBorderUnderlined: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse); - break; - case annotBorderBeveled: - case annotBorderInset: - appearBuf->appendf("{0:.2f} w\n", 0.5 * w); - setColor(obj1.getArray(), gFalse, 0); - drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse); - setColor(obj1.getArray(), gFalse, - borderStyle->getType() == annotBorderBeveled ? 1 : -1); - drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w); - setColor(obj1.getArray(), gFalse, - borderStyle->getType() == annotBorderBeveled ? -1 : 1); - drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w); - break; - } - - } else { - switch (borderStyle->getType()) { - case annotBorderDashed: - appearBuf->append("["); - borderStyle->getDash(&dash, &dashLength); - for (i = 0; i < dashLength; ++i) { - appearBuf->appendf(" {0:.2f}", dash[i]); - } - appearBuf->append("] 0 d\n"); - // fall through to the solid case - case annotBorderSolid: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n", - 0.5 * w, dx - w, dy - w); - break; - case annotBorderBeveled: - case annotBorderInset: - setColor(obj1.getArray(), gTrue, - borderStyle->getType() == annotBorderBeveled ? 1 : -1); - appearBuf->append("0 0 m\n"); - appearBuf->appendf("0 {0:.2f} l\n", dy); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", w, dy - w); - appearBuf->appendf("{0:.2f} {0:.2f} l\n", w); - appearBuf->append("f\n"); - setColor(obj1.getArray(), gTrue, - borderStyle->getType() == annotBorderBeveled ? -1 : 1); - appearBuf->append("0 0 m\n"); - appearBuf->appendf("{0:.2f} 0 l\n", dx); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, w); - appearBuf->appendf("{0:.2f} {0:.2f} l\n", w); - appearBuf->append("f\n"); - break; - case annotBorderUnderlined: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - appearBuf->appendf("0 0 m {0:.2f} 0 l s\n", dx); - break; - } - - // clip to the inside of the border - appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", - w, dx - 2 * w, dy - 2 * w); - } - } + //----- get line properties + if (annotObj.dictLookup("L", &obj1)->isArray() && + obj1.arrayGetLength() == 4) { + if (obj1.arrayGet(0, &obj2)->isNum()) { + x1 = obj2.getNum(); + } else { + obj2.free(); obj1.free(); + goto err1; } + obj2.free(); + if (obj1.arrayGet(1, &obj2)->isNum()) { + y1 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + if (obj1.arrayGet(2, &obj2)->isNum()) { + x2 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + if (obj1.arrayGet(3, &obj2)->isNum()) { + y2 = obj2.getNum(); + } else { + obj2.free(); + obj1.free(); + goto err1; + } + obj2.free(); + } else { + obj1.free(); + goto err1; } - - // get the resource dictionary - fieldLookup(field, acroForm, "DR", &drObj); - - // build the font dictionary - if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) { - fontDict = new GfxFontDict(doc->getXRef(), NULL, obj1.getDict()); + obj1.free(); + lineEnd1 = lineEnd2 = annotLineEndNone; + if (annotObj.dictLookup("LE", &obj1)->isArray() && + obj1.arrayGetLength() == 2) { + lineEnd1 = parseLineEndType(obj1.arrayGet(0, &obj2)); + obj2.free(); + lineEnd2 = parseLineEndType(obj1.arrayGet(1, &obj2)); + obj2.free(); + } + obj1.free(); + if (annotObj.dictLookup("LL", &obj1)->isNum()) { + leaderLen = obj1.getNum(); } else { - fontDict = NULL; + leaderLen = 0; } obj1.free(); - - // get the default appearance string - if (fieldLookup(field, acroForm, "DA", &obj1)->isNull()) { - obj1.free(); - acroForm->lookup("DA", &obj1); + if (annotObj.dictLookup("LLE", &obj1)->isNum()) { + leaderExtLen = obj1.getNum(); + } else { + leaderExtLen = 0; } - if (obj1.isString()) { - da = obj1.getString()->copy(); + obj1.free(); + if (annotObj.dictLookup("LLO", &obj1)->isNum()) { + leaderOffLen = obj1.getNum(); } else { - da = NULL; + leaderOffLen = 0; } obj1.free(); - // get the rotation value - rot = 0; - if (mkDict) { - if (mkDict->lookup("R", &obj1)->isInt()) { - rot = obj1.getInt(); - } - obj1.free(); + //----- compute positions + x1 -= xMin; + y1 -= yMin; + x2 -= xMin; + y2 -= yMin; + dx = x2 - x1; + dy = y2 - y1; + len = sqrt(dx*dx + dy*dy); + if (len > 0) { + dx /= len; + dy /= len; } - - // draw the field contents - if (ftObj.isName("Btn")) { - caption = NULL; - if (mkDict) { - if (mkDict->lookup("CA", &obj1)->isString()) { - caption = obj1.getString()->copy(); - } - obj1.free(); - } - // radio button - if (ff & fieldFlagRadio) { - //~ Acrobat doesn't draw a caption if there is no AP dict (?) - if (fieldLookup(field, acroForm, "V", &obj1) - ->isName(appearanceState->getCString())) { - if (caption) { - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gTrue, rot); - } else { - if (mkDict) { - if (mkDict->lookup("BC", &obj2)->isArray() && - obj2.arrayGetLength() > 0) { - dx = xMax - xMin; - dy = yMax - yMin; - setColor(obj2.getArray(), gTrue, 0); - drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), - gTrue); - } - obj2.free(); - } - } - } - obj1.free(); - // pushbutton - } else if (ff & fieldFlagPushbutton) { - if (caption) { - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gFalse, rot); - } - // checkbox - } else { - fieldLookup(field, acroForm, "V", &obj1); - if (obj1.isName() && !obj1.isName("Off")) { - if (!caption) { - caption = new GString("3"); // ZapfDingbats checkmark - } - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gTrue, rot); - } - obj1.free(); - } - if (caption) { - delete caption; - } - } else if (ftObj.isName("Tx")) { - //~ value strings can be Unicode - if (!fieldLookup(field, acroForm, "V", &obj1)->isString()) { - obj1.free(); - fieldLookup(field, acroForm, "DV", &obj1); - } - if (obj1.isString()) { - if (fieldLookup(field, acroForm, "Q", &obj2)->isInt()) { - quadding = obj2.getInt(); - } else { - quadding = fieldQuadLeft; - } - obj2.free(); - comb = 0; - if (ff & fieldFlagComb) { - if (fieldLookup(field, acroForm, "MaxLen", &obj2)->isInt()) { - comb = obj2.getInt(); - } - obj2.free(); - } - drawText(obj1.getString(), da, fontDict, - ff & fieldFlagMultiline, comb, quadding, gTrue, gFalse, rot); - } - obj1.free(); - } else if (ftObj.isName("Ch")) { - //~ value/option strings can be Unicode - if (fieldLookup(field, acroForm, "Q", &obj1)->isInt()) { - quadding = obj1.getInt(); - } else { - quadding = fieldQuadLeft; - } - obj1.free(); - // combo box - if (ff & fieldFlagCombo) { - if (fieldLookup(field, acroForm, "V", &obj1)->isString()) { - drawText(obj1.getString(), da, fontDict, - gFalse, 0, quadding, gTrue, gFalse, rot); - //~ Acrobat draws a popup icon on the right side - } - obj1.free(); - // list box - } else { - if (field->lookup("Opt", &obj1)->isArray()) { - nOptions = obj1.arrayGetLength(); - // get the option text - text = (GString **)gmallocn(nOptions, sizeof(GString *)); - for (i = 0; i < nOptions; ++i) { - text[i] = NULL; - obj1.arrayGet(i, &obj2); - if (obj2.isString()) { - text[i] = obj2.getString()->copy(); - } else if (obj2.isArray() && obj2.arrayGetLength() == 2) { - if (obj2.arrayGet(1, &obj3)->isString()) { - text[i] = obj3.getString()->copy(); - } - obj3.free(); - } - obj2.free(); - if (!text[i]) { - text[i] = new GString(); - } - } - // get the selected option(s) - selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); - //~ need to use the I field in addition to the V field - fieldLookup(field, acroForm, "V", &obj2); - for (i = 0; i < nOptions; ++i) { - selection[i] = gFalse; - if (obj2.isString()) { - if (!obj2.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - } else if (obj2.isArray()) { - for (j = 0; j < obj2.arrayGetLength(); ++j) { - if (obj2.arrayGet(j, &obj3)->isString() && - !obj3.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - obj3.free(); - } - } - } - obj2.free(); - // get the top index - if (field->lookup("TI", &obj2)->isInt()) { - topIdx = obj2.getInt(); - } else { - topIdx = 0; - } - obj2.free(); - // draw the text - drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding); - for (i = 0; i < nOptions; ++i) { - delete text[i]; - } - gfree(text); - gfree(selection); - } - obj1.free(); - } - } else if (ftObj.isName("Sig")) { - //~unimp + if (leaderLen != 0) { + ax1 = x1 + leaderOffLen * dy; + ay1 = y1 - leaderOffLen * dx; + lx1 = ax1 + leaderLen * dy; + ly1 = ay1 - leaderLen * dx; + bx1 = lx1 + leaderExtLen * dy; + by1 = ly1 - leaderExtLen * dx; + ax2 = x2 + leaderOffLen * dy; + ay2 = y2 - leaderOffLen * dx; + lx2 = ax2 + leaderLen * dy; + ly2 = ay2 - leaderLen * dx; + bx2 = lx2 + leaderExtLen * dy; + by2 = ly2 - leaderExtLen * dx; } else { - error(errSyntaxError, -1, "Unknown field type"); + lx1 = x1; + ly1 = y1; + lx2 = x2; + ly2 = y2; + ax1 = ay1 = ax2 = ay2 = 0; // make gcc happy + bx1 = by1 = bx2 = by2 = 0; + } + adjustLineEndpoint(lineEnd1, lx1, ly1, dx, dy, w, &tx1, &ty1); + adjustLineEndpoint(lineEnd2, lx2, ly2, -dx, -dy, w, &tx2, &ty2); + + //----- draw leaders + if (leaderLen != 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + ax1, ay1, bx1, by1); + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + ax2, ay2 , bx2, by2); } - if (da) { - delete da; + //----- draw the line + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + tx1, ty1, tx2, ty2); + appearBuf->append("S\n"); + + //----- draw the arrows + if (borderStyle->getType() == annotBorderDashed) { + appearBuf->append("[] 0 d\n"); } + drawLineArrow(lineEnd1, lx1, ly1, dx, dy, w, fill); + drawLineArrow(lineEnd2, lx2, ly2, -dx, -dy, w, fill); - // build the appearance stream dictionary + //----- build the appearance stream dictionary appearDict.initDict(doc->getXRef()); appearDict.dictAdd(copyString("Length"), obj1.initInt(appearBuf->getLength())); @@ -658,757 +490,482 @@ void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) { obj1.arrayAdd(obj2.initReal(xMax - xMin)); obj1.arrayAdd(obj2.initReal(yMax - yMin)); appearDict.dictAdd(copyString("BBox"), &obj1); - - // set the resource dictionary - if (drObj.isDict()) { - appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1)); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); } - drObj.free(); - // build the appearance stream + //----- build the appearance stream appearStream = new MemStream(appearBuf->getCString(), 0, appearBuf->getLength(), &appearDict); appearance.free(); appearance.initStream(appearStream); - if (fontDict) { - delete fontDict; - } - ftObj.free(); - mkObj.free(); + err1: + annotObj.free(); } -// Set the current fill or stroke color, based on <a> (which should -// have 1, 3, or 4 elements). If <adjust> is +1, color is brightened; -// if <adjust> is -1, color is darkened; otherwise color is not -// modified. -void Annot::setColor(Array *a, GBool fill, int adjust) { - Object obj1; - double color[4]; - int nComps, i; +//~ this doesn't handle line ends (arrows) +void Annot::generatePolyLineAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1, w; + int i; - nComps = a->getLength(); - if (nComps > 4) { - nComps = 4; - } - for (i = 0; i < nComps && i < 4; ++i) { - if (a->get(i, &obj1)->isNum()) { - color[i] = obj1.getNum(); - } else { - color[i] = 0; - } - obj1.free(); - } - if (nComps == 4) { - adjust = -adjust; - } - if (adjust > 0) { - for (i = 0; i < nComps; ++i) { - color[i] = 0.5 * color[i] + 0.5; - } - } else if (adjust < 0) { - for (i = 0; i < nComps; ++i) { - color[i] = 0.5 * color[i]; - } - } - if (nComps == 4) { - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", - color[0], color[1], color[2], color[3], - fill ? 'k' : 'K'); - } else if (nComps == 3) { - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", - color[0], color[1], color[2], - fill ? "rg" : "RG"); - } else { - appearBuf->appendf("{0:.2f} {1:c}\n", - color[0], - fill ? 'g' : 'G'); + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; } -} -// Draw the variable text or caption for a field. -void Annot::drawText(GString *text, GString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, - GBool txField, GBool forceZapfDingbats, int rot) { - GString *text2; - GList *daToks; - GString *tok; - GfxFont *font; - double dx, dy; - double fontSize, fontSize2, border, x, xPrev, y, w, w2, wMax; - int tfPos, tmPos, i, j, k, c; - - //~ if there is no MK entry, this should use the existing content stream, - //~ and only replace the marked content portion of it - //~ (this is only relevant for Tx fields) - - // check for a Unicode string - //~ this currently drops all non-Latin1 characters - if (text->getLength() >= 2 && - text->getChar(0) == '\xfe' && text->getChar(1) == '\xff') { - text2 = new GString(); - for (i = 2; i+1 < text->getLength(); i += 2) { - c = ((text->getChar(i) & 0xff) << 8) + (text->getChar(i+1) & 0xff); - if (c <= 0xff) { - text2->append((char)c); - } else { - text2->append('?'); - } - } - } else { - text2 = text; - } + appearBuf = new GString(); - // parse the default appearance string - tfPos = tmPos = -1; - if (da) { - daToks = new GList(); - i = 0; - while (i < da->getLength()) { - while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { - ++i; - } - if (i < da->getLength()) { - for (j = i + 1; - j < da->getLength() && !Lexer::isSpace(da->getChar(j)); - ++j) ; - daToks->append(new GString(da, i, j - i)); - i = j; - } - } - for (i = 2; i < daToks->getLength(); ++i) { - if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { - tfPos = i - 2; - } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { - tmPos = i - 6; - } - } - } else { - daToks = NULL; + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); } + obj1.free(); - // force ZapfDingbats - //~ this should create the font if needed (?) - if (forceZapfDingbats) { - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos); - if (tok->cmp("/ZaDb")) { - tok->clear(); - tok->append("/ZaDb"); - } - } + //----- set line style, colors + setLineStyle(borderStyle, &w); + setStrokeColor(borderStyle->getColor(), borderStyle->getNumColorComps()); + // fill = gFalse; + // if (annotObj.dictLookup("IC", &obj1)->isArray()) { + // if (setFillColor(&obj1)) { + // fill = gTrue; + // } + // } + // obj1.free(); + + //----- draw line + if (!annotObj.dictLookup("Vertices", &obj1)->isArray()) { + obj1.free(); + goto err1; } - - // get the font and font size - font = NULL; - fontSize = 0; - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos); - if (tok->getLength() >= 1 && tok->getChar(0) == '/') { - if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { - error(errSyntaxError, -1, "Unknown font in field's DA string"); - } + for (i = 0; i+1 < obj1.arrayGetLength(); i += 2) { + if (!obj1.arrayGet(i, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; + } + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(i+1, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; + } + y1 = obj2.getNum(); + obj2.free(); + x1 -= xMin; + y1 -= yMin; + if (i == 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x1, y1); } else { - error(errSyntaxError, -1, - "Invalid font name in 'Tf' operator in field's DA string"); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x1, y1); } - tok = (GString *)daToks->get(tfPos + 1); - fontSize = atof(tok->getCString()); - } else { - error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); } + appearBuf->append("S\n"); + obj1.free(); - // get the border width - border = borderStyle->getWidth(); - - // setup - if (txField) { - appearBuf->append("/Tx BMC\n"); - } - appearBuf->append("q\n"); - if (rot == 90) { - appearBuf->appendf("0 1 -1 0 {0:.2f} 0 cm\n", xMax - xMin); - dx = yMax - yMin; - dy = xMax - xMin; - } else if (rot == 180) { - appearBuf->appendf("-1 0 0 -1 {0:.2f} {1:.2f} cm\n", - xMax - xMin, yMax - yMin); - dx = xMax - yMax; - dy = yMax - yMin; - } else if (rot == 270) { - appearBuf->appendf("0 -1 1 0 0 {0:.2f} cm\n", yMax - yMin); - dx = yMax - yMin; - dy = xMax - xMin; - } else { // assume rot == 0 - dx = xMax - xMin; - dy = yMax - yMin; + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(xMax - xMin)); + obj1.arrayAdd(obj2.initReal(yMax - yMin)); + appearDict.dictAdd(copyString("BBox"), &obj1); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); } - appearBuf->append("BT\n"); - - // multi-line text - if (multiline) { - // note: the comb flag is ignored in multiline mode - - wMax = dx - 2 * border - 4; - - // compute font autosize - if (fontSize == 0) { - for (fontSize = 20; fontSize > 1; --fontSize) { - y = dy - 3; - w2 = 0; - i = 0; - while (i < text2->getLength()) { - getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); - if (w > w2) { - w2 = w; - } - i = k; - y -= fontSize; - } - // approximate the descender for the last line - if (y >= 0.33 * fontSize) { - break; - } - } - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } - - // starting y coordinate - // (note: each line of text starts with a Td operator that moves - // down a line) - y = dy - 3; - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->append('0'); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } - - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GString *)daToks->get(i))->append(' '); - } - } - - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 0 {0:.2f} Tm\n", y); - } - - // write a series of lines of text - i = 0; - xPrev = 0; - while (i < text2->getLength()) { - - getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); - - // compute text start position - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (dx - w) / 2; - break; - case fieldQuadRight: - x = dx - border - 2 - w; - break; - } - - // draw the line - appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize); - appearBuf->append('('); - for (; i < j; ++i) { - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); - } - } - appearBuf->append(") Tj\n"); - - // next line - i = k; - xPrev = x; - } - - // single-line text - } else { - //~ replace newlines with spaces? - what does Acrobat do? - - // comb formatting - if (comb > 0) { - - // compute comb spacing - w = (dx - 2 * border) / comb; - - // compute font autosize - if (fontSize == 0) { - fontSize = dy - 2 * border; - if (w < fontSize) { - fontSize = w; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } - // compute text start position - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = border + 2 + 0.5 * (comb - text2->getLength()) * w; - break; - case fieldQuadRight: - x = border + 2 + (comb - text2->getLength()) * w; - break; - } - y = 0.5 * dy - 0.4 * fontSize; - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->appendf("{0:.2f}", x); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } - - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GString *)daToks->get(i))->append(' '); - } - } - - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); - } - - // write the text string - //~ this should center (instead of left-justify) each character within - //~ its comb cell - for (i = 0; i < text2->getLength(); ++i) { - if (i > 0) { - appearBuf->appendf("{0:.2f} 0 Td\n", w); - } - appearBuf->append('('); - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("{0:.2f} 0 Td\n", w); - } else { - appearBuf->append(c); - } - appearBuf->append(") Tj\n"); - } - - // regular (non-comb) formatting - } else { - - // compute string width - if (font && !font->isCIDFont()) { - w = 0; - for (i = 0; i < text2->getLength(); ++i) { - w += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i)); - } - } else { - // otherwise, make a crude estimate - w = text2->getLength() * 0.5; - } + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); - // compute font autosize - if (fontSize == 0) { - fontSize = dy - 2 * border; - fontSize2 = (dx - 4 - 2 * border) / w; - if (fontSize2 < fontSize) { - fontSize = fontSize2; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } + err1: + annotObj.free(); +} - // compute text start position - w *= fontSize; - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (dx - w) / 2; - break; - case fieldQuadRight: - x = dx - border - 2 - w; - break; - } - y = 0.5 * dy - 0.4 * fontSize; - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->appendf("{0:.2f}", x); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } +void Annot::generatePolygonAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1; + int i; - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GString *)daToks->get(i))->append(' '); - } - } + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; + } - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); - } + appearBuf = new GString(); - // write the text string - appearBuf->append('('); - for (i = 0; i < text2->getLength(); ++i) { - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); - } - } - appearBuf->append(") Tj\n"); - } + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); } + obj1.free(); - // cleanup - appearBuf->append("ET\n"); - appearBuf->append("Q\n"); - if (txField) { - appearBuf->append("EMC\n"); + //----- set fill color + if (!annotObj.dictLookup("IC", &obj1)->isArray() || + !setFillColor(&obj1)) { + obj1.free(); + goto err1; } + obj1.free(); - if (daToks) { - deleteGList(daToks, GString); - } - if (text2 != text) { - delete text2; + //----- fill polygon + if (!annotObj.dictLookup("Vertices", &obj1)->isArray()) { + obj1.free(); + goto err1; } -} - -// Draw the variable text or caption for a field. -void Annot::drawListBox(GString **text, GBool *selection, - int nOptions, int topIdx, - GString *da, GfxFontDict *fontDict, GBool quadding) { - GList *daToks; - GString *tok; - GfxFont *font; - double fontSize, fontSize2, border, x, y, w, wMax; - int tfPos, tmPos, i, j, c; - - //~ if there is no MK entry, this should use the existing content stream, - //~ and only replace the marked content portion of it - //~ (this is only relevant for Tx fields) - - // parse the default appearance string - tfPos = tmPos = -1; - if (da) { - daToks = new GList(); - i = 0; - while (i < da->getLength()) { - while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { - ++i; - } - if (i < da->getLength()) { - for (j = i + 1; - j < da->getLength() && !Lexer::isSpace(da->getChar(j)); - ++j) ; - daToks->append(new GString(da, i, j - i)); - i = j; - } + for (i = 0; i+1 < obj1.arrayGetLength(); i += 2) { + if (!obj1.arrayGet(i, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; } - for (i = 2; i < daToks->getLength(); ++i) { - if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { - tfPos = i - 2; - } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { - tmPos = i - 6; - } + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(i+1, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; } - } else { - daToks = NULL; - } - - // get the font and font size - font = NULL; - fontSize = 0; - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos); - if (tok->getLength() >= 1 && tok->getChar(0) == '/') { - if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { - error(errSyntaxError, -1, "Unknown font in field's DA string"); - } + y1 = obj2.getNum(); + obj2.free(); + x1 -= xMin; + y1 -= yMin; + if (i == 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x1, y1); } else { - error(errSyntaxError, -1, - "Invalid font name in 'Tf' operator in field's DA string"); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x1, y1); } - tok = (GString *)daToks->get(tfPos + 1); - fontSize = atof(tok->getCString()); - } else { - error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); } + appearBuf->append("f\n"); + obj1.free(); - // get the border width - border = borderStyle->getWidth(); - - // compute font autosize - if (fontSize == 0) { - wMax = 0; - for (i = 0; i < nOptions; ++i) { - if (font && !font->isCIDFont()) { - w = 0; - for (j = 0; j < text[i]->getLength(); ++j) { - w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); - } - } else { - // otherwise, make a crude estimate - w = text[i]->getLength() * 0.5; - } - if (w > wMax) { - wMax = w; - } - } - fontSize = yMax - yMin - 2 * border; - fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax; - if (fontSize2 < fontSize) { - fontSize = fontSize2; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(xMax - xMin)); + obj1.arrayAdd(obj2.initReal(yMax - yMin)); + appearDict.dictAdd(copyString("BBox"), &obj1); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); } - // draw the text - y = yMax - yMin - 1.1 * fontSize; - for (i = topIdx; i < nOptions; ++i) { - - // setup - appearBuf->append("q\n"); - - // draw the background if selected - if (selection[i]) { - appearBuf->append("0 g f\n"); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n", - border, - y - 0.2 * fontSize, - xMax - xMin - 2 * border, - 1.1 * fontSize); - } - - // setup - appearBuf->append("BT\n"); - - // compute string width - if (font && !font->isCIDFont()) { - w = 0; - for (j = 0; j < text[i]->getLength(); ++j) { - w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); - } - } else { - // otherwise, make a crude estimate - w = text[i]->getLength() * 0.5; - } - - // compute text start position - w *= fontSize; - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (xMax - xMin - w) / 2; - break; - case fieldQuadRight: - x = xMax - xMin - border - 2 - w; - break; - } - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->appendf("{0:.2f}", x); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } - - // write the DA string - if (daToks) { - for (j = 0; j < daToks->getLength(); ++j) { - appearBuf->append((GString *)daToks->get(j))->append(' '); - } - } + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); - } + err1: + annotObj.free(); +} - // change the text color if selected - if (selection[i]) { - appearBuf->append("1 g\n"); - } +void Annot::setLineStyle(AnnotBorderStyle *bs, double *lineWidth) { + double *dash; + double w; + int dashLength, i; - // write the text string - appearBuf->append('('); - for (j = 0; j < text[i]->getLength(); ++j) { - c = text[i]->getChar(j) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); - } + if ((w = borderStyle->getWidth()) <= 0) { + w = 0.1; + } + *lineWidth = w; + appearBuf->appendf("{0:.4f} w\n", w); + // this treats beveled/inset/underline as solid + if (borderStyle->getType() == annotBorderDashed) { + borderStyle->getDash(&dash, &dashLength); + appearBuf->append("["); + for (i = 0; i < dashLength; ++i) { + appearBuf->appendf(" {0:.4f}", dash[i]); } - appearBuf->append(") Tj\n"); - - // cleanup - appearBuf->append("ET\n"); - appearBuf->append("Q\n"); - - // next line - y -= 1.1 * fontSize; + appearBuf->append("] 0 d\n"); } + appearBuf->append("0 j\n0 J\n"); +} - if (daToks) { - deleteGList(daToks, GString); +void Annot::setStrokeColor(double *color, int nComps) { + switch (nComps) { + case 0: + appearBuf->append("0 G\n"); + break; + case 1: + appearBuf->appendf("{0:.2f} G\n", color[0]); + break; + case 3: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} RG\n", + color[0], color[1], color[2]); + break; + case 4: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} K\n", + color[0], color[1], color[2], color[3]); + break; } } -// Figure out how much text will fit on the next line. Returns: -// *end = one past the last character to be included -// *width = width of the characters start .. end-1 -// *next = index of first character on the following line -void Annot::getNextLine(GString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next) { - double w, dw; - int j, k, c; - - // figure out how much text will fit on the line - //~ what does Adobe do with tabs? - w = 0; - for (j = start; j < text->getLength() && w <= wMax; ++j) { - c = text->getChar(j) & 0xff; - if (c == 0x0a || c == 0x0d) { - break; - } - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; +GBool Annot::setFillColor(Object *colorObj) { + Object obj; + double color[4]; + int i; + + if (!colorObj->isArray()) { + return gFalse; + } + for (i = 0; i < colorObj->arrayGetLength(); ++i) { + if (colorObj->arrayGet(i, &obj)->isNum()) { + color[i] = obj.getNum(); } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; + color[i] = 0; } - w += dw; + obj.free(); } - if (w > wMax) { - for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; - for (; k > start && text->getChar(k-1) == ' '; --k) ; - if (k > start) { - j = k; - } - if (j == start) { - // handle the pathological case where the first character is - // too wide to fit on the line all by itself - j = start + 1; - } + switch (colorObj->arrayGetLength()) { + case 1: + appearBuf->appendf("{0:.2f} g\n", color[0]); + return gTrue; + case 3: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} rg\n", + color[0], color[1], color[2]); + return gTrue; + case 4: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.3f} k\n", + color[0], color[1], + color[2], color[3]); + return gTrue; } - *end = j; + return gFalse; +} - // compute the width - w = 0; - for (k = start; k < j; ++k) { - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; - } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; - } - w += dw; +AnnotLineEndType Annot::parseLineEndType(Object *obj) { + if (obj->isName("None")) { + return annotLineEndNone; + } else if (obj->isName("Square")) { + return annotLineEndSquare; + } else if (obj->isName("Circle")) { + return annotLineEndCircle; + } else if (obj->isName("Diamond")) { + return annotLineEndDiamond; + } else if (obj->isName("OpenArrow")) { + return annotLineEndOpenArrow; + } else if (obj->isName("ClosedArrow")) { + return annotLineEndClosedArrow; + } else if (obj->isName("Butt")) { + return annotLineEndButt; + } else if (obj->isName("ROpenArrow")) { + return annotLineEndROpenArrow; + } else if (obj->isName("RClosedArrow")) { + return annotLineEndRClosedArrow; + } else if (obj->isName("Slash")) { + return annotLineEndSlash; + } else { + return annotLineEndNone; } - *width = w; +} - // next line - while (j < text->getLength() && text->getChar(j) == ' ') { - ++j; - } - if (j < text->getLength() && text->getChar(j) == 0x0d) { - ++j; +void Annot::adjustLineEndpoint(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, double *tx, double *ty) { + switch (lineEnd) { + case annotLineEndNone: + w = 0; + break; + case annotLineEndSquare: + w *= lineEndSize1; + break; + case annotLineEndCircle: + w *= lineEndSize1; + break; + case annotLineEndDiamond: + w *= lineEndSize1; + break; + case annotLineEndOpenArrow: + w = 0; + break; + case annotLineEndClosedArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndButt: + w = 0; + break; + case annotLineEndROpenArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndRClosedArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndSlash: + w = 0; + break; } - if (j < text->getLength() && text->getChar(j) == 0x0a) { - ++j; + *tx = x + w * dx; + *ty = y + w * dy; +} + +void Annot::drawLineArrow(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, GBool fill) { + switch (lineEnd) { + case annotLineEndNone: + break; + case annotLineEndSquare: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*dx + 0.5*w*dy, + y + w*dy - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dy, + y - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*dy, + y + 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*dx - 0.5*w*dy, + y + w*dy + 0.5*w*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndCircle: + w *= lineEndSize1; + drawCircle(x + 0.5*w*dx, y + 0.5*w*dy, 0.5*w, fill ? "b" : "s"); + break; + case annotLineEndDiamond: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dx - 0.5*w*dy, + y + 0.5*w*dy + 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*dx, + y + w*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dx + 0.5*w*dy, + y + 0.5*w*dy - 0.5*w*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndOpenArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*cos(lineArrowAngle)*dx + w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx - w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy + w*sin(lineArrowAngle)*dx); + appearBuf->append("S\n"); + break; + case annotLineEndClosedArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*cos(lineArrowAngle)*dx + w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx - w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy + w*sin(lineArrowAngle)*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndButt: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + 0.5*w*dy, + y - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*dy, + y + 0.5*w*dx); + appearBuf->append("S\n"); + break; + case annotLineEndROpenArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*sin(lineArrowAngle)*dy, + y - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx, + y + w*cos(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - w*sin(lineArrowAngle)*dy, + y + w*sin(lineArrowAngle)*dx); + appearBuf->append("S\n"); + break; + case annotLineEndRClosedArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*sin(lineArrowAngle)*dy, + y - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx, + y + w*cos(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - w*sin(lineArrowAngle)*dy, + y + w*sin(lineArrowAngle)*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndSlash: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + 0.5*w*cos(lineArrowAngle)*dy + - 0.5*w*sin(lineArrowAngle)*dx, + y - 0.5*w*cos(lineArrowAngle)*dx + - 0.5*w*sin(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*cos(lineArrowAngle)*dy + + 0.5*w*sin(lineArrowAngle)*dx, + y + 0.5*w*cos(lineArrowAngle)*dx + + 0.5*w*sin(lineArrowAngle)*dy); + appearBuf->append("S\n"); + break; } - *next = j; } // Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>). -// If <fill> is true, the circle is filled; otherwise it is stroked. -void Annot::drawCircle(double cx, double cy, double r, GBool fill) { - appearBuf->appendf("{0:.2f} {1:.2f} m\n", +// <cmd> is used to draw the circle ("f", "s", or "b"). +void Annot::drawCircle(double cx, double cy, double r, const char *cmd) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", cx + r, cy); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + r, cy + bezierCircle * r, cx + bezierCircle * r, cy + r, cx, cy + r); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - bezierCircle * r, cy + r, cx - r, cy + bezierCircle * r, cx - r, cy); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - r, cy - bezierCircle * r, cx - bezierCircle * r, cy - r, cx, cy - r); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + bezierCircle * r, cy - r, cx + r, cy - bezierCircle * r, cx + r, cy); - appearBuf->append(fill ? "f\n" : "s\n"); + appearBuf->appendf("{0:s}\n", cmd); } // Draw the top-left half of an (approximate) circle of radius <r> @@ -1417,16 +974,16 @@ void Annot::drawCircleTopLeft(double cx, double cy, double r) { double r2; r2 = r / sqrt(2.0); - appearBuf->appendf("{0:.2f} {1:.2f} m\n", + appearBuf->appendf("{0:.4f} {1:.4f} m\n", cx + r2, cy + r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + (1 - bezierCircle) * r2, cy + (1 + bezierCircle) * r2, cx - (1 - bezierCircle) * r2, cy + (1 + bezierCircle) * r2, cx - r2, cy + r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - (1 + bezierCircle) * r2, cy + (1 - bezierCircle) * r2, cx - (1 + bezierCircle) * r2, @@ -1442,16 +999,16 @@ void Annot::drawCircleBottomRight(double cx, double cy, double r) { double r2; r2 = r / sqrt(2.0); - appearBuf->appendf("{0:.2f} {1:.2f} m\n", + appearBuf->appendf("{0:.4f} {1:.4f} m\n", cx - r2, cy - r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - (1 - bezierCircle) * r2, cy - (1 + bezierCircle) * r2, cx + (1 - bezierCircle) * r2, cy - (1 + bezierCircle) * r2, cx + r2, cy - r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + (1 + bezierCircle) * r2, cy - (1 - bezierCircle) * r2, cx + (1 + bezierCircle) * r2, @@ -1461,32 +1018,7 @@ void Annot::drawCircleBottomRight(double cx, double cy, double r) { appearBuf->append("S\n"); } -// Look up an inheritable field dictionary entry. -Object *Annot::fieldLookup(Dict *field, Dict *acroForm, - const char *key, Object *obj) { - Dict *dict; - Object parent; - - dict = field; - if (!dict->lookup(key, obj)->isNull()) { - return obj; - } - obj->free(); - if (dict->lookup("Parent", &parent)->isDict()) { - fieldLookup(parent.getDict(), acroForm, key, obj); - } else if (acroForm) { - // some fields don't specify a parent, so we check the AcroForm - // dictionary just in case - fieldLookup(acroForm, NULL, key, obj); - } else { - obj->initNull(); - } - parent.free(); - return obj; -} - void Annot::draw(Gfx *gfx, GBool printing) { - Object obj; GBool oc, isLink; // check the flags @@ -1503,10 +1035,8 @@ void Annot::draw(Gfx *gfx, GBool printing) { // draw the appearance stream isLink = type && !type->cmp("Link"); - appearance.fetch(doc->getXRef(), &obj); - gfx->drawAnnot(&obj, isLink ? borderStyle : (AnnotBorderStyle *)NULL, + gfx->drawAnnot(&appearance, isLink ? borderStyle : (AnnotBorderStyle *)NULL, xMin, yMin, xMax, yMax); - obj.free(); } Object *Annot::getObject(Object *obj) { @@ -1524,8 +1054,9 @@ Object *Annot::getObject(Object *obj) { Annots::Annots(PDFDoc *docA, Object *annotsObj) { Annot *annot; - Object obj1; + Object obj1, obj2; Ref ref; + GBool drawWidgetAnnots; int size; int i; @@ -1535,6 +1066,13 @@ Annots::Annots(PDFDoc *docA, Object *annotsObj) { nAnnots = 0; if (annotsObj->isArray()) { + // Kludge: some PDF files define an empty AcroForm, but still + // include Widget-type annotations -- in that case, we want to + // draw the widgets (since the form code won't). This really + // ought to look for Widget-type annotations that are not included + // in any form field. + drawWidgetAnnots = !doc->getCatalog()->getForm() || + doc->getCatalog()->getForm()->getNumFields() == 0; for (i = 0; i < annotsObj->arrayGetLength(); ++i) { if (annotsObj->arrayGetNF(i, &obj1)->isRef()) { ref = obj1.getRef(); @@ -1544,16 +1082,20 @@ Annots::Annots(PDFDoc *docA, Object *annotsObj) { ref.num = ref.gen = -1; } if (obj1.isDict()) { - annot = new Annot(doc, obj1.getDict(), &ref); - if (annot->isOk()) { - if (nAnnots >= size) { - size += 16; - annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + if (drawWidgetAnnots || + !obj1.dictLookup("Subtype", &obj2)->isName("Widget")) { + annot = new Annot(doc, obj1.getDict(), &ref); + if (annot->isOk()) { + if (nAnnots >= size) { + size += 16; + annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + } + annots[nAnnots++] = annot; + } else { + delete annot; } - annots[nAnnots++] = annot; - } else { - delete annot; } + obj2.free(); } obj1.free(); } @@ -1569,69 +1111,11 @@ Annots::~Annots() { gfree(annots); } -void Annots::generateAppearances() { - Dict *acroForm; - Object obj1, obj2; - Ref ref; +void Annots::generateAnnotAppearances() { int i; - acroForm = doc->getCatalog()->getAcroForm()->isDict() ? - doc->getCatalog()->getAcroForm()->getDict() : NULL; - if (acroForm->lookup("Fields", &obj1)->isArray()) { - for (i = 0; i < obj1.arrayGetLength(); ++i) { - if (obj1.arrayGetNF(i, &obj2)->isRef()) { - ref = obj2.getRef(); - obj2.free(); - obj1.arrayGet(i, &obj2); - } else { - ref.num = ref.gen = -1; - } - if (obj2.isDict()) { - scanFieldAppearances(obj2.getDict(), &ref, NULL, acroForm); - } - obj2.free(); - } - } - obj1.free(); -} - -void Annots::scanFieldAppearances(Dict *node, Ref *ref, Dict *parent, - Dict *acroForm) { - Annot *annot; - Object obj1, obj2; - Ref ref2; - int i; - - // non-terminal node: scan the children - if (node->lookup("Kids", &obj1)->isArray()) { - for (i = 0; i < obj1.arrayGetLength(); ++i) { - if (obj1.arrayGetNF(i, &obj2)->isRef()) { - ref2 = obj2.getRef(); - obj2.free(); - obj1.arrayGet(i, &obj2); - } else { - ref2.num = ref2.gen = -1; - } - if (obj2.isDict()) { - scanFieldAppearances(obj2.getDict(), &ref2, node, acroForm); - } - obj2.free(); - } - obj1.free(); - return; - } - obj1.free(); - - // terminal node: this is either a combined annot/field dict, or an - // annot dict whose parent is a field - if ((annot = findAnnot(ref))) { - node->lookupNF("Parent", &obj1); - if (!parent || !obj1.isNull()) { - annot->generateFieldAppearance(node, node, acroForm); - } else { - annot->generateFieldAppearance(parent, node, acroForm); - } - obj1.free(); + for (i = 0; i < nAnnots; ++i) { + annots[i]->generateAnnotAppearance(); } } diff --git a/xpdf/Annot.h b/xpdf/Annot.h index 987e80e..6f52ac4 100644 --- a/xpdf/Annot.h +++ b/xpdf/Annot.h @@ -38,15 +38,15 @@ public: AnnotBorderStyle(AnnotBorderType typeA, double widthA, double *dashA, int dashLengthA, - double rA, double gA, double bA); + double *colorA, int nColorCompsA); ~AnnotBorderStyle(); AnnotBorderType getType() { return type; } double getWidth() { return width; } void getDash(double **dashA, int *dashLengthA) { *dashA = dash; *dashLengthA = dashLength; } - void getColor(double *rA, double *gA, double *bA) - { *rA = r; *gA = g; *bA = b; } + int getNumColorComps() { return nColorComps; } + double *getColor() { return color; } private: @@ -54,7 +54,23 @@ private: double width; double *dash; int dashLength; - double r, g, b; + double color[4]; + int nColorComps; +}; + +//------------------------------------------------------------------------ + +enum AnnotLineEndType { + annotLineEndNone, + annotLineEndSquare, + annotLineEndCircle, + annotLineEndDiamond, + annotLineEndOpenArrow, + annotLineEndClosedArrow, + annotLineEndButt, + annotLineEndROpenArrow, + annotLineEndRClosedArrow, + annotLineEndSlash }; //------------------------------------------------------------------------ @@ -85,25 +101,26 @@ public: GBool match(Ref *refA) { return ref.num == refA->num && ref.gen == refA->gen; } - void generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm); + void generateAnnotAppearance(); private: - void setColor(Array *a, GBool fill, int adjust); - void drawText(GString *text, GString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, - GBool txField, GBool forceZapfDingbats, int rot); - void drawListBox(GString **text, GBool *selection, - int nOptions, int topIdx, - GString *da, GfxFontDict *fontDict, GBool quadding); - void getNextLine(GString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next); - void drawCircle(double cx, double cy, double r, GBool fill); + void generateLineAppearance(); + void generatePolyLineAppearance(); + void generatePolygonAppearance(); + void setLineStyle(AnnotBorderStyle *bs, double *lineWidth); + void setStrokeColor(double *color, int nComps); + GBool setFillColor(Object *colorObj); + AnnotLineEndType parseLineEndType(Object *obj); + void adjustLineEndpoint(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, double *tx, double *ty); + void drawLineArrow(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, GBool fill); + void drawCircle(double cx, double cy, double r, const char *cmd); void drawCircleTopLeft(double cx, double cy, double r); void drawCircleBottomRight(double cx, double cy, double r); - Object *fieldLookup(Dict *field, Dict *acroForm, - const char *key, Object *obj); PDFDoc *doc; XRef *xref; // the xref table for this PDF file @@ -137,9 +154,9 @@ public: int getNumAnnots() { return nAnnots; } Annot *getAnnot(int i) { return annots[i]; } - // (Re)generate the appearance streams for all annotations belonging - // to a form field. - void generateAppearances(); + // Generate an appearance stream for any non-form-field annotation + // that is missing it. + void generateAnnotAppearances(); private: diff --git a/xpdf/CMap.cc b/xpdf/CMap.cc index bb80bdd..affdf93 100644 --- a/xpdf/CMap.cc +++ b/xpdf/CMap.cc @@ -283,34 +283,36 @@ void CMap::copyVector(CMapVectorEntry *dest, CMapVectorEntry *src) { void CMap::addCIDs(Guint start, Guint end, Guint nBytes, CID firstCID) { CMapVectorEntry *vec; - CID cid; - int byte; - Guint i, j; - - vec = vector; - for (i = nBytes - 1; i >= 1; --i) { - byte = (start >> (8 * i)) & 0xff; - if (!vec[byte].isVector) { - vec[byte].isVector = gTrue; - vec[byte].vector = - (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry)); - for (j = 0; j < 256; ++j) { - vec[byte].vector[j].isVector = gFalse; - vec[byte].vector[j].cid = 0; + int byte, byte0, byte1; + Guint start1, end1, i, j, k; + + start1 = start & 0xffffff00; + end1 = end & 0xffffff00; + for (i = start1; i <= end1; i += 0x100) { + vec = vector; + for (j = nBytes - 1; j >= 1; --j) { + byte = (i >> (8 * j)) & 0xff; + if (!vec[byte].isVector) { + vec[byte].isVector = gTrue; + vec[byte].vector = + (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry)); + for (k = 0; k < 256; ++k) { + vec[byte].vector[k].isVector = gFalse; + vec[byte].vector[k].cid = 0; + } } + vec = vec[byte].vector; } - vec = vec[byte].vector; - } - cid = firstCID; - for (byte = (int)(start & 0xff); byte <= (int)(end & 0xff); ++byte) { - if (vec[byte].isVector) { - error(errSyntaxError, -1, - "Invalid CID ({0:x} - {1:x} [{2:d} bytes]) in CMap", - start, end, nBytes); - } else { - vec[byte].cid = cid; + byte0 = (i < start) ? (start & 0xff) : 0; + byte1 = (i + 0xff > end) ? (end & 0xff) : 0xff; + for (byte = byte0; byte <= byte1; ++byte) { + if (vec[byte].isVector) { + error(errSyntaxError, -1, "Invalid CID ({0:x} [{1:d} bytes]) in CMap", + i, nBytes); + } else { + vec[byte].cid = firstCID + ((i + byte) - start); + } } - ++cid; } } diff --git a/xpdf/Catalog.cc b/xpdf/Catalog.cc index 6c8a703..e2e2a87 100644 --- a/xpdf/Catalog.cc +++ b/xpdf/Catalog.cc @@ -2,7 +2,7 @@ // // Catalog.cc // -// Copyright 1996-2007 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -27,7 +27,8 @@ #include "Page.h" #include "Error.h" #include "Link.h" -#include "PDFDocEncoding.h" +#include "Form.h" +#include "TextString.h" #include "Catalog.h" //------------------------------------------------------------------------ @@ -69,23 +70,20 @@ PageTreeNode::~PageTreeNode() { class EmbeddedFile { public: - EmbeddedFile(Unicode *nameA, int nameLenA, Object *streamRefA); + EmbeddedFile(TextString *nameA, Object *streamRefA); ~EmbeddedFile(); - Unicode *name; - int nameLen; + TextString *name; Object streamRef; }; -EmbeddedFile::EmbeddedFile(Unicode *nameA, int nameLenA, - Object *streamRefA) { +EmbeddedFile::EmbeddedFile(TextString *nameA, Object *streamRefA) { name = nameA; - nameLen = nameLenA; streamRefA->copy(&streamRef); } EmbeddedFile::~EmbeddedFile() { - gfree(name); + delete name; streamRef.free(); } @@ -105,6 +103,7 @@ Catalog::Catalog(PDFDoc *docA) { pageRefs = NULL; numPages = 0; baseURI = NULL; + form = NULL; embeddedFiles = NULL; xref->getCatalog(&catDict); @@ -165,6 +164,10 @@ Catalog::Catalog(PDFDoc *docA) { // get the AcroForm dictionary catDict.dictLookup("AcroForm", &acroForm); + if (!acroForm.isNull()) { + form = Form::load(doc, this, &acroForm); + } + // get the OCProperties dictionary catDict.dictLookup("OCProperties", &ocProperties); @@ -205,6 +208,9 @@ Catalog::~Catalog() { structTreeRoot.free(); outline.free(); acroForm.free(); + if (form) { + delete form; + } ocProperties.free(); if (embeddedFiles) { deleteGList(embeddedFiles, EmbeddedFile); @@ -236,7 +242,8 @@ GString *Catalog::readMetadata() { GString *s; Dict *dict; Object obj; - int c; + char buf[4096]; + int n; if (!metadata.isStream()) { return NULL; @@ -249,8 +256,8 @@ GString *Catalog::readMetadata() { obj.free(); s = new GString(); metadata.streamReset(); - while ((c = metadata.streamGetChar()) != EOF) { - s->append(c); + while ((n = metadata.streamGetBlock(buf, sizeof(buf))) > 0) { + s->append(buf, n); } metadata.streamClose(); return s; @@ -615,6 +622,12 @@ void Catalog::readFileAttachmentAnnots(Object *pageNodeRef, Object pageNode, kids, kid, annots, annot, subtype, fileSpec, contents; int i; + // check for an invalid object reference (e.g., in a damaged PDF file) + if (pageNodeRef->getRefNum() < 0 || + pageNodeRef->getRefNum() >= xref->getNumObjects()) { + return; + } + // check for a page tree loop if (pageNodeRef->isRef()) { if (touchedObjs[pageNodeRef->getRefNum()]) { @@ -661,42 +674,22 @@ void Catalog::readFileAttachmentAnnots(Object *pageNodeRef, void Catalog::readEmbeddedFile(Object *fileSpec, Object *name1) { Object name2, efObj, streamObj; GString *s; - Unicode *name; - int nameLen, i; + TextString *name; if (fileSpec->isDict()) { if (fileSpec->dictLookup("UF", &name2)->isString()) { - s = name2.getString(); + name = new TextString(name2.getString()); } else { name2.free(); if (fileSpec->dictLookup("F", &name2)->isString()) { - s = name2.getString(); + name = new TextString(name2.getString()); } else if (name1 && name1->isString()) { - s = name1->getString(); - } else { - s = NULL; - } - } - if (s) { - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - nameLen = (s->getLength() - 2) / 2; - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } + name = new TextString(name1->getString()); } else { - nameLen = s->getLength(); - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } + s = new GString("?"); + name = new TextString(s); + delete s; } - } else { - nameLen = 1; - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - name[0] = '?'; } name2.free(); if (fileSpec->dictLookup("EF", &efObj)->isDict()) { @@ -704,13 +697,13 @@ void Catalog::readEmbeddedFile(Object *fileSpec, Object *name1) { if (!embeddedFiles) { embeddedFiles = new GList(); } - embeddedFiles->append(new EmbeddedFile(name, nameLen, &streamObj)); + embeddedFiles->append(new EmbeddedFile(name, &streamObj)); } else { - gfree(name); + delete name; } streamObj.free(); } else { - gfree(name); + delete name; } efObj.free(); } @@ -721,11 +714,15 @@ int Catalog::getNumEmbeddedFiles() { } Unicode *Catalog::getEmbeddedFileName(int idx) { - return ((EmbeddedFile *)embeddedFiles->get(idx))->name; + return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getUnicode(); } int Catalog::getEmbeddedFileNameLength(int idx) { - return ((EmbeddedFile *)embeddedFiles->get(idx))->nameLen; + return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getLength(); +} + +Object *Catalog::getEmbeddedFileStreamRef(int idx) { + return &((EmbeddedFile *)embeddedFiles->get(idx))->streamRef; } Object *Catalog::getEmbeddedFileStreamObj(int idx, Object *strObj) { diff --git a/xpdf/Catalog.h b/xpdf/Catalog.h index efbbeda..7f064aa 100644 --- a/xpdf/Catalog.h +++ b/xpdf/Catalog.h @@ -26,6 +26,7 @@ class PageAttrs; struct Ref; class LinkDest; class PageTreeNode; +class Form; //------------------------------------------------------------------------ // Catalog @@ -82,12 +83,15 @@ public: Object *getAcroForm() { return &acroForm; } + Form *getForm() { return form; } + Object *getOCProperties() { return &ocProperties; } // Get the list of embedded files. int getNumEmbeddedFiles(); Unicode *getEmbeddedFileName(int idx); int getEmbeddedFileNameLength(int idx); + Object *getEmbeddedFileStreamRef(int idx); Object *getEmbeddedFileStreamObj(int idx, Object *strObj); private: @@ -106,6 +110,7 @@ private: Object structTreeRoot; // structure tree root dictionary Object outline; // outline dictionary Object acroForm; // AcroForm dictionary + Form *form; // parsed form Object ocProperties; // OCProperties dictionary GList *embeddedFiles; // embedded file list [EmbeddedFile] GBool ok; // true if catalog is valid diff --git a/xpdf/CharCodeToUnicode.cc b/xpdf/CharCodeToUnicode.cc index ff40595..674b992 100644 --- a/xpdf/CharCodeToUnicode.cc +++ b/xpdf/CharCodeToUnicode.cc @@ -167,7 +167,7 @@ CharCodeToUnicode *CharCodeToUnicode::parseUnicodeToUnicode( while (getLine(buf, sizeof(buf), f)) { ++line; if (!(tok = strtok(buf, " \t\r\n")) || - !parseHex(tok, strlen(tok), &u0)) { + !parseHex(tok, (int)strlen(tok), &u0)) { error(errSyntaxWarning, -1, "Bad line ({0:d}) in unicodeToUnicode file '{1:t}'", line, fileName); @@ -178,7 +178,7 @@ CharCodeToUnicode *CharCodeToUnicode::parseUnicodeToUnicode( if (!(tok = strtok(NULL, " \t\r\n"))) { break; } - if (!parseHex(tok, strlen(tok), &uBuf[n])) { + if (!parseHex(tok, (int)strlen(tok), &uBuf[n])) { error(errSyntaxWarning, -1, "Bad line ({0:d}) in unicodeToUnicode file '{1:t}'", line, fileName); @@ -336,23 +336,21 @@ void CharCodeToUnicode::parseCMap1(int (*getCharFunc)(void *), void *data, if (code1 > maxCode || code2 > maxCode) { error(errSyntaxWarning, -1, "Invalid entry in bfrange block in ToUnicode CMap"); - if (code1 > maxCode) { - code1 = maxCode; - } if (code2 > maxCode) { code2 = maxCode; } } if (!strcmp(tok3, "[")) { i = 0; - while (pst->getToken(tok1, sizeof(tok1), &n1) && - code1 + i <= code2) { + while (pst->getToken(tok1, sizeof(tok1), &n1)) { if (!strcmp(tok1, "]")) { break; } if (tok1[0] == '<' && tok1[n1 - 1] == '>') { - tok1[n1 - 1] = '\0'; - addMapping(code1 + i, tok1 + 1, n1 - 2, 0); + if (code1 + i <= code2) { + tok1[n1 - 1] = '\0'; + addMapping(code1 + i, tok1 + 1, n1 - 2, 0); + } } else { error(errSyntaxWarning, -1, "Illegal entry in bfrange block in ToUnicode CMap"); diff --git a/xpdf/CharCodeToUnicode.h b/xpdf/CharCodeToUnicode.h index b4ccd04..1b870ef 100644 --- a/xpdf/CharCodeToUnicode.h +++ b/xpdf/CharCodeToUnicode.h @@ -74,6 +74,8 @@ public: // code supported by the mapping. CharCode getLength() { return mapLen; } + GBool isIdentity() { return !map; } + private: void parseCMap1(int (*getCharFunc)(void *), void *data, int nBits); diff --git a/xpdf/Decrypt.cc b/xpdf/Decrypt.cc index afde3c3..bf0bfb7 100644 --- a/xpdf/Decrypt.cc +++ b/xpdf/Decrypt.cc @@ -16,13 +16,12 @@ #include "gmem.h" #include "Decrypt.h" -static void aesKeyExpansion(DecryptAESState *s, - Guchar *objKey, int objKeyLen); -static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last); static void aes256KeyExpansion(DecryptAES256State *s, Guchar *objKey, int objKeyLen); static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last); static void sha256(Guchar *msg, int msgLen, Guchar *hash); +static void sha384(Guchar *msg, int msgLen, Guchar *hash); +static void sha512(Guchar *msg, int msgLen, Guchar *hash); static Guchar passwordPad[32] = { 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, @@ -45,6 +44,7 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, DecryptAES256State state; Guchar test[127 + 56], test2[32]; GString *userPassword2; + const char *userPW; Guchar fState[256]; Guchar tmpKey[16]; Guchar fx, fy; @@ -52,7 +52,7 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, *ownerPasswordOk = gFalse; - if (encRevision == 5) { + if (encRevision == 5 || encRevision == 6) { // check the owner password if (ownerPassword) { @@ -65,6 +65,10 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, memcpy(test + len, ownerKey->getCString() + 32, 8); memcpy(test + len + 8, userKey->getCString(), 48); sha256(test, len + 56, test); + if (encRevision == 6) { + r6Hash(test, 32, ownerPassword->getCString(), len, + userKey->getCString()); + } if (!memcmp(test, ownerKey->getCString(), 32)) { // compute the file key from the owner password @@ -72,6 +76,10 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, memcpy(test + len, ownerKey->getCString() + 40, 8); memcpy(test + len + 8, userKey->getCString(), 48); sha256(test, len + 56, test); + if (encRevision == 6) { + r6Hash(test, 32, ownerPassword->getCString(), len, + userKey->getCString()); + } aes256KeyExpansion(&state, test, 32); for (i = 0; i < 16; ++i) { state.cbc[i] = 0; @@ -90,34 +98,45 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, // check the user password if (userPassword) { //~ this is supposed to convert the password to UTF-8 using "SASLprep" + userPW = userPassword->getCString(); len = userPassword->getLength(); if (len > 127) { len = 127; } - memcpy(test, userPassword->getCString(), len); - memcpy(test + len, userKey->getCString() + 32, 8); - sha256(test, len + 8, test); - if (!memcmp(test, userKey->getCString(), 32)) { - - // compute the file key from the user password - memcpy(test, userPassword->getCString(), len); - memcpy(test + len, userKey->getCString() + 40, 8); - sha256(test, len + 8, test); - aes256KeyExpansion(&state, test, 32); - for (i = 0; i < 16; ++i) { - state.cbc[i] = 0; - } - aes256DecryptBlock(&state, (Guchar *)userEnc->getCString(), gFalse); - memcpy(fileKey, state.buf, 16); - aes256DecryptBlock(&state, (Guchar *)userEnc->getCString() + 16, - gFalse); - memcpy(fileKey + 16, state.buf, 16); + } else { + userPW = ""; + len = 0; + } + memcpy(test, userPW, len); + memcpy(test + len, userKey->getCString() + 32, 8); + sha256(test, len + 8, test); + if (encRevision == 6) { + r6Hash(test, 32, userPW, len, NULL); + } + if (!memcmp(test, userKey->getCString(), 32)) { - return gTrue; + // compute the file key from the user password + memcpy(test, userPW, len); + memcpy(test + len, userKey->getCString() + 40, 8); + sha256(test, len + 8, test); + if (encRevision == 6) { + r6Hash(test, 32, userPW, len, NULL); } + aes256KeyExpansion(&state, test, 32); + for (i = 0; i < 16; ++i) { + state.cbc[i] = 0; + } + aes256DecryptBlock(&state, (Guchar *)userEnc->getCString(), gFalse); + memcpy(fileKey, state.buf, 16); + aes256DecryptBlock(&state, (Guchar *)userEnc->getCString() + 16, + gFalse); + memcpy(fileKey + 16, state.buf, 16); + + return gTrue; } return gFalse; + } else { // try using the supplied owner password to generate the user password @@ -172,6 +191,61 @@ GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength, } } +void Decrypt::r6Hash(Guchar *key, int keyLen, const char *pwd, int pwdLen, + char *userKey) { + Guchar key1[64*(127+64+48)]; + DecryptAESState state128; + int n, i, j, k; + + i = 0; + while (1) { + memcpy(key1, pwd, pwdLen); + memcpy(key1 + pwdLen, key, keyLen); + n = pwdLen + keyLen; + if (userKey) { + memcpy(key1 + pwdLen + keyLen, userKey, 48); + n += 48; + } + for (j = 1; j < 64; ++j) { + memcpy(key1 + j * n, key1, n); + } + n *= 64; + aesKeyExpansion(&state128, key, 16, gFalse); + for (j = 0; j < 16; ++j) { + state128.cbc[j] = key[16+j]; + } + for (j = 0; j < n; j += 16) { + aesEncryptBlock(&state128, key1 + j); + memcpy(key1 + j, state128.buf, 16); + } + k = 0; + for (j = 0; j < 16; ++j) { + k += key1[j] % 3; + } + k %= 3; + switch (k) { + case 0: + sha256(key1, n, key); + keyLen = 32; + break; + case 1: + sha384(key1, n, key); + keyLen = 48; + break; + case 2: + sha512(key1, n, key); + keyLen = 64; + break; + } + // from the spec, it appears that i should be incremented after + // the test, but that doesn't match what Adobe does + ++i; + if (i >= 64 && key1[n - 1] <= i - 32) { + break; + } + } +} + GBool Decrypt::makeFileKey2(int encVersion, int encRevision, int keyLength, GString *ownerKey, GString *userKey, int permissions, GString *fileID, @@ -305,8 +379,6 @@ DecryptStream::~DecryptStream() { } void DecryptStream::reset() { - int i; - str->reset(); switch (algo) { case cryptRC4: @@ -315,17 +387,13 @@ void DecryptStream::reset() { state.rc4.buf = EOF; break; case cryptAES: - aesKeyExpansion(&state.aes, objKey, objKeyLength); - for (i = 0; i < 16; ++i) { - state.aes.cbc[i] = str->getChar(); - } + aesKeyExpansion(&state.aes, objKey, objKeyLength, gTrue); + str->getBlock((char *)state.aes.cbc, 16); state.aes.bufIdx = 16; break; case cryptAES256: aes256KeyExpansion(&state.aes256, objKey, objKeyLength); - for (i = 0; i < 16; ++i) { - state.aes256.cbc[i] = str->getChar(); - } + str->getBlock((char *)state.aes256.cbc, 16); state.aes256.bufIdx = 16; break; } @@ -333,7 +401,7 @@ void DecryptStream::reset() { int DecryptStream::getChar() { Guchar in[16]; - int c, i; + int c; c = EOF; // make gcc happy switch (algo) { @@ -350,11 +418,8 @@ int DecryptStream::getChar() { break; case cryptAES: if (state.aes.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = (Guchar)c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aesDecryptBlock(&state.aes, in, str->lookChar() == EOF); } @@ -366,11 +431,8 @@ int DecryptStream::getChar() { break; case cryptAES256: if (state.aes256.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = (Guchar)c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF); } @@ -386,7 +448,7 @@ int DecryptStream::getChar() { int DecryptStream::lookChar() { Guchar in[16]; - int c, i; + int c; c = EOF; // make gcc happy switch (algo) { @@ -402,11 +464,8 @@ int DecryptStream::lookChar() { break; case cryptAES: if (state.aes.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aesDecryptBlock(&state.aes, in, str->lookChar() == EOF); } @@ -418,11 +477,8 @@ int DecryptStream::lookChar() { break; case cryptAES256: if (state.aes256.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF); } @@ -540,6 +596,14 @@ static inline Guint rotWord(Guint x) { return ((x << 8) & 0xffffffff) | (x >> 24); } +static inline void subBytes(Guchar *state) { + int i; + + for (i = 0; i < 16; ++i) { + state[i] = sbox[state[i]]; + } +} + static inline void invSubBytes(Guchar *state) { int i; @@ -548,6 +612,29 @@ static inline void invSubBytes(Guchar *state) { } } +static inline void shiftRows(Guchar *state) { + Guchar t; + + t = state[4]; + state[4] = state[5]; + state[5] = state[6]; + state[6] = state[7]; + state[7] = t; + + t = state[8]; + state[8] = state[10]; + state[10] = t; + t = state[9]; + state[9] = state[11]; + state[11] = t; + + t = state[15]; + state[15] = state[14]; + state[14] = state[13]; + state[13] = state[12]; + state[12] = t; +} + static inline void invShiftRows(Guchar *state) { Guchar t; @@ -571,6 +658,22 @@ static inline void invShiftRows(Guchar *state) { state[15] = t; } +// {02} \cdot s +static inline Guchar mul02(Guchar s) { + Guchar s2; + + s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1); + return s2; +} + +// {03} \cdot s +static inline Guchar mul03(Guchar s) { + Guchar s2; + + s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1); + return s ^ s2; +} + // {09} \cdot s static inline Guchar mul09(Guchar s) { Guchar s2, s4, s8; @@ -611,6 +714,22 @@ static inline Guchar mul0e(Guchar s) { return s2 ^ s4 ^ s8; } +static inline void mixColumns(Guchar *state) { + int c; + Guchar s0, s1, s2, s3; + + for (c = 0; c < 4; ++c) { + s0 = state[c]; + s1 = state[4+c]; + s2 = state[8+c]; + s3 = state[12+c]; + state[c] = mul02(s0) ^ mul03(s1) ^ s2 ^ s3; + state[4+c] = s0 ^ mul02(s1) ^ mul03(s2) ^ s3; + state[8+c] = s0 ^ s1 ^ mul02(s2) ^ mul03(s3); + state[12+c] = mul03(s0) ^ s1 ^ s2 ^ mul02(s3); + } +} + static inline void invMixColumns(Guchar *state) { int c; Guchar s0, s1, s2, s3; @@ -654,8 +773,9 @@ static inline void addRoundKey(Guchar *state, Guint *w) { } } -static void aesKeyExpansion(DecryptAESState *s, - Guchar *objKey, int objKeyLen) { +void aesKeyExpansion(DecryptAESState *s, + Guchar *objKey, int objKeyLen, + GBool decrypt) { Guint temp; int i, round; @@ -672,12 +792,50 @@ static void aesKeyExpansion(DecryptAESState *s, } s->w[i] = s->w[i-4] ^ temp; } + if (decrypt) { + for (round = 1; round <= 9; ++round) { + invMixColumnsW(&s->w[round * 4]); + } + } +} + +void aesEncryptBlock(DecryptAESState *s, Guchar *in) { + int c, round; + + // initial state + CBC + for (c = 0; c < 4; ++c) { + s->state[c] = in[4*c] ^ s->cbc[4*c]; + s->state[4+c] = in[4*c+1] ^ s->cbc[4*c+1]; + s->state[8+c] = in[4*c+2] ^ s->cbc[4*c+2]; + s->state[12+c] = in[4*c+3] ^ s->cbc[4*c+3]; + } + + // round 0 + addRoundKey(s->state, &s->w[0]); + + // rounds 1 .. 9 for (round = 1; round <= 9; ++round) { - invMixColumnsW(&s->w[round * 4]); + subBytes(s->state); + shiftRows(s->state); + mixColumns(s->state); + addRoundKey(s->state, &s->w[round * 4]); + } + + // round 10 + subBytes(s->state); + shiftRows(s->state); + addRoundKey(s->state, &s->w[10 * 4]); + + // output + save for next CBC + for (c = 0; c < 4; ++c) { + s->buf[4*c] = s->cbc[4*c] = s->state[c]; + s->buf[4*c+1] = s->cbc[4*c+1] = s->state[4+c]; + s->buf[4*c+2] = s->cbc[4*c+2] = s->state[8+c]; + s->buf[4*c+3] = s->cbc[4*c+3] = s->state[12+c]; } } -static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) { +void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) { int c, round, n, i; // initial state @@ -844,151 +1002,187 @@ static inline Gulong md5Round4(Gulong a, Gulong b, Gulong c, Gulong d, return b + rotateLeft((a + (c ^ (b | ~d)) + Xk + Ti), s); } -void md5(Guchar *msg, int msgLen, Guchar *digest) { +void md5Start(MD5State *state) { + state->a = 0x67452301; + state->b = 0xefcdab89; + state->c = 0x98badcfe; + state->d = 0x10325476; + state->bufLen = 0; + state->msgLen = 0; +} + +static void md5ProcessBlock(MD5State *state) { Gulong x[16]; - Gulong a, b, c, d, aa, bb, cc, dd; - int n64; - int i, j, k; + Gulong a, b, c, d; + int i; - // sanity check - if (msgLen < 0) { - return; + for (i = 0; i < 16; ++i) { + x[i] = state->buf[4*i] | (state->buf[4*i+1] << 8) | + (state->buf[4*i+2] << 16) | (state->buf[4*i+3] << 24); } - // compute number of 64-byte blocks - // (length + pad byte (0x80) + 8 bytes for length) - n64 = (msgLen + 1 + 8 + 63) / 64; - - // initialize a, b, c, d - a = 0x67452301; - b = 0xefcdab89; - c = 0x98badcfe; - d = 0x10325476; - - // loop through blocks - k = 0; - for (i = 0; i < n64; ++i) { - - // grab a 64-byte block - for (j = 0; j < 16 && k < msgLen - 3; ++j, k += 4) - x[j] = (((((msg[k+3] << 8) + msg[k+2]) << 8) + msg[k+1]) << 8) + msg[k]; - if (i == n64 - 1) { - if (k == msgLen - 3) - x[j] = 0x80000000 + (((msg[k+2] << 8) + msg[k+1]) << 8) + msg[k]; - else if (k == msgLen - 2) - x[j] = 0x800000 + (msg[k+1] << 8) + msg[k]; - else if (k == msgLen - 1) - x[j] = 0x8000 + msg[k]; - else - x[j] = 0x80; - ++j; - while (j < 16) - x[j++] = 0; - x[14] = msgLen << 3; - } + a = state->a; + b = state->b; + c = state->c; + d = state->d; + + // round 1 + a = md5Round1(a, b, c, d, x[0], 7, 0xd76aa478); + d = md5Round1(d, a, b, c, x[1], 12, 0xe8c7b756); + c = md5Round1(c, d, a, b, x[2], 17, 0x242070db); + b = md5Round1(b, c, d, a, x[3], 22, 0xc1bdceee); + a = md5Round1(a, b, c, d, x[4], 7, 0xf57c0faf); + d = md5Round1(d, a, b, c, x[5], 12, 0x4787c62a); + c = md5Round1(c, d, a, b, x[6], 17, 0xa8304613); + b = md5Round1(b, c, d, a, x[7], 22, 0xfd469501); + a = md5Round1(a, b, c, d, x[8], 7, 0x698098d8); + d = md5Round1(d, a, b, c, x[9], 12, 0x8b44f7af); + c = md5Round1(c, d, a, b, x[10], 17, 0xffff5bb1); + b = md5Round1(b, c, d, a, x[11], 22, 0x895cd7be); + a = md5Round1(a, b, c, d, x[12], 7, 0x6b901122); + d = md5Round1(d, a, b, c, x[13], 12, 0xfd987193); + c = md5Round1(c, d, a, b, x[14], 17, 0xa679438e); + b = md5Round1(b, c, d, a, x[15], 22, 0x49b40821); + + // round 2 + a = md5Round2(a, b, c, d, x[1], 5, 0xf61e2562); + d = md5Round2(d, a, b, c, x[6], 9, 0xc040b340); + c = md5Round2(c, d, a, b, x[11], 14, 0x265e5a51); + b = md5Round2(b, c, d, a, x[0], 20, 0xe9b6c7aa); + a = md5Round2(a, b, c, d, x[5], 5, 0xd62f105d); + d = md5Round2(d, a, b, c, x[10], 9, 0x02441453); + c = md5Round2(c, d, a, b, x[15], 14, 0xd8a1e681); + b = md5Round2(b, c, d, a, x[4], 20, 0xe7d3fbc8); + a = md5Round2(a, b, c, d, x[9], 5, 0x21e1cde6); + d = md5Round2(d, a, b, c, x[14], 9, 0xc33707d6); + c = md5Round2(c, d, a, b, x[3], 14, 0xf4d50d87); + b = md5Round2(b, c, d, a, x[8], 20, 0x455a14ed); + a = md5Round2(a, b, c, d, x[13], 5, 0xa9e3e905); + d = md5Round2(d, a, b, c, x[2], 9, 0xfcefa3f8); + c = md5Round2(c, d, a, b, x[7], 14, 0x676f02d9); + b = md5Round2(b, c, d, a, x[12], 20, 0x8d2a4c8a); + + // round 3 + a = md5Round3(a, b, c, d, x[5], 4, 0xfffa3942); + d = md5Round3(d, a, b, c, x[8], 11, 0x8771f681); + c = md5Round3(c, d, a, b, x[11], 16, 0x6d9d6122); + b = md5Round3(b, c, d, a, x[14], 23, 0xfde5380c); + a = md5Round3(a, b, c, d, x[1], 4, 0xa4beea44); + d = md5Round3(d, a, b, c, x[4], 11, 0x4bdecfa9); + c = md5Round3(c, d, a, b, x[7], 16, 0xf6bb4b60); + b = md5Round3(b, c, d, a, x[10], 23, 0xbebfbc70); + a = md5Round3(a, b, c, d, x[13], 4, 0x289b7ec6); + d = md5Round3(d, a, b, c, x[0], 11, 0xeaa127fa); + c = md5Round3(c, d, a, b, x[3], 16, 0xd4ef3085); + b = md5Round3(b, c, d, a, x[6], 23, 0x04881d05); + a = md5Round3(a, b, c, d, x[9], 4, 0xd9d4d039); + d = md5Round3(d, a, b, c, x[12], 11, 0xe6db99e5); + c = md5Round3(c, d, a, b, x[15], 16, 0x1fa27cf8); + b = md5Round3(b, c, d, a, x[2], 23, 0xc4ac5665); + + // round 4 + a = md5Round4(a, b, c, d, x[0], 6, 0xf4292244); + d = md5Round4(d, a, b, c, x[7], 10, 0x432aff97); + c = md5Round4(c, d, a, b, x[14], 15, 0xab9423a7); + b = md5Round4(b, c, d, a, x[5], 21, 0xfc93a039); + a = md5Round4(a, b, c, d, x[12], 6, 0x655b59c3); + d = md5Round4(d, a, b, c, x[3], 10, 0x8f0ccc92); + c = md5Round4(c, d, a, b, x[10], 15, 0xffeff47d); + b = md5Round4(b, c, d, a, x[1], 21, 0x85845dd1); + a = md5Round4(a, b, c, d, x[8], 6, 0x6fa87e4f); + d = md5Round4(d, a, b, c, x[15], 10, 0xfe2ce6e0); + c = md5Round4(c, d, a, b, x[6], 15, 0xa3014314); + b = md5Round4(b, c, d, a, x[13], 21, 0x4e0811a1); + a = md5Round4(a, b, c, d, x[4], 6, 0xf7537e82); + d = md5Round4(d, a, b, c, x[11], 10, 0xbd3af235); + c = md5Round4(c, d, a, b, x[2], 15, 0x2ad7d2bb); + b = md5Round4(b, c, d, a, x[9], 21, 0xeb86d391); + + // increment a, b, c, d + state->a += a; + state->b += b; + state->c += c; + state->d += d; + + state->bufLen = 0; +} - // save a, b, c, d - aa = a; - bb = b; - cc = c; - dd = d; - - // round 1 - a = md5Round1(a, b, c, d, x[0], 7, 0xd76aa478); - d = md5Round1(d, a, b, c, x[1], 12, 0xe8c7b756); - c = md5Round1(c, d, a, b, x[2], 17, 0x242070db); - b = md5Round1(b, c, d, a, x[3], 22, 0xc1bdceee); - a = md5Round1(a, b, c, d, x[4], 7, 0xf57c0faf); - d = md5Round1(d, a, b, c, x[5], 12, 0x4787c62a); - c = md5Round1(c, d, a, b, x[6], 17, 0xa8304613); - b = md5Round1(b, c, d, a, x[7], 22, 0xfd469501); - a = md5Round1(a, b, c, d, x[8], 7, 0x698098d8); - d = md5Round1(d, a, b, c, x[9], 12, 0x8b44f7af); - c = md5Round1(c, d, a, b, x[10], 17, 0xffff5bb1); - b = md5Round1(b, c, d, a, x[11], 22, 0x895cd7be); - a = md5Round1(a, b, c, d, x[12], 7, 0x6b901122); - d = md5Round1(d, a, b, c, x[13], 12, 0xfd987193); - c = md5Round1(c, d, a, b, x[14], 17, 0xa679438e); - b = md5Round1(b, c, d, a, x[15], 22, 0x49b40821); - - // round 2 - a = md5Round2(a, b, c, d, x[1], 5, 0xf61e2562); - d = md5Round2(d, a, b, c, x[6], 9, 0xc040b340); - c = md5Round2(c, d, a, b, x[11], 14, 0x265e5a51); - b = md5Round2(b, c, d, a, x[0], 20, 0xe9b6c7aa); - a = md5Round2(a, b, c, d, x[5], 5, 0xd62f105d); - d = md5Round2(d, a, b, c, x[10], 9, 0x02441453); - c = md5Round2(c, d, a, b, x[15], 14, 0xd8a1e681); - b = md5Round2(b, c, d, a, x[4], 20, 0xe7d3fbc8); - a = md5Round2(a, b, c, d, x[9], 5, 0x21e1cde6); - d = md5Round2(d, a, b, c, x[14], 9, 0xc33707d6); - c = md5Round2(c, d, a, b, x[3], 14, 0xf4d50d87); - b = md5Round2(b, c, d, a, x[8], 20, 0x455a14ed); - a = md5Round2(a, b, c, d, x[13], 5, 0xa9e3e905); - d = md5Round2(d, a, b, c, x[2], 9, 0xfcefa3f8); - c = md5Round2(c, d, a, b, x[7], 14, 0x676f02d9); - b = md5Round2(b, c, d, a, x[12], 20, 0x8d2a4c8a); - - // round 3 - a = md5Round3(a, b, c, d, x[5], 4, 0xfffa3942); - d = md5Round3(d, a, b, c, x[8], 11, 0x8771f681); - c = md5Round3(c, d, a, b, x[11], 16, 0x6d9d6122); - b = md5Round3(b, c, d, a, x[14], 23, 0xfde5380c); - a = md5Round3(a, b, c, d, x[1], 4, 0xa4beea44); - d = md5Round3(d, a, b, c, x[4], 11, 0x4bdecfa9); - c = md5Round3(c, d, a, b, x[7], 16, 0xf6bb4b60); - b = md5Round3(b, c, d, a, x[10], 23, 0xbebfbc70); - a = md5Round3(a, b, c, d, x[13], 4, 0x289b7ec6); - d = md5Round3(d, a, b, c, x[0], 11, 0xeaa127fa); - c = md5Round3(c, d, a, b, x[3], 16, 0xd4ef3085); - b = md5Round3(b, c, d, a, x[6], 23, 0x04881d05); - a = md5Round3(a, b, c, d, x[9], 4, 0xd9d4d039); - d = md5Round3(d, a, b, c, x[12], 11, 0xe6db99e5); - c = md5Round3(c, d, a, b, x[15], 16, 0x1fa27cf8); - b = md5Round3(b, c, d, a, x[2], 23, 0xc4ac5665); - - // round 4 - a = md5Round4(a, b, c, d, x[0], 6, 0xf4292244); - d = md5Round4(d, a, b, c, x[7], 10, 0x432aff97); - c = md5Round4(c, d, a, b, x[14], 15, 0xab9423a7); - b = md5Round4(b, c, d, a, x[5], 21, 0xfc93a039); - a = md5Round4(a, b, c, d, x[12], 6, 0x655b59c3); - d = md5Round4(d, a, b, c, x[3], 10, 0x8f0ccc92); - c = md5Round4(c, d, a, b, x[10], 15, 0xffeff47d); - b = md5Round4(b, c, d, a, x[1], 21, 0x85845dd1); - a = md5Round4(a, b, c, d, x[8], 6, 0x6fa87e4f); - d = md5Round4(d, a, b, c, x[15], 10, 0xfe2ce6e0); - c = md5Round4(c, d, a, b, x[6], 15, 0xa3014314); - b = md5Round4(b, c, d, a, x[13], 21, 0x4e0811a1); - a = md5Round4(a, b, c, d, x[4], 6, 0xf7537e82); - d = md5Round4(d, a, b, c, x[11], 10, 0xbd3af235); - c = md5Round4(c, d, a, b, x[2], 15, 0x2ad7d2bb); - b = md5Round4(b, c, d, a, x[9], 21, 0xeb86d391); - - // increment a, b, c, d - a += aa; - b += bb; - c += cc; - d += dd; +void md5Append(MD5State *state, Guchar *data, int dataLen) { + Guchar *p; + int remain, k; + + p = data; + remain = dataLen; + while (state->bufLen + remain >= 64) { + k = 64 - state->bufLen; + memcpy(state->buf + state->bufLen, p, k); + state->bufLen = 64; + md5ProcessBlock(state); + p += k; + remain -= k; } + if (remain > 0) { + memcpy(state->buf + state->bufLen, p, remain); + state->bufLen += remain; + } + state->msgLen += dataLen; +} + +void md5Finish(MD5State *state) { + // padding and length + state->buf[state->bufLen++] = 0x80; + if (state->bufLen > 56) { + while (state->bufLen < 64) { + state->buf[state->bufLen++] = 0x00; + } + md5ProcessBlock(state); + } + while (state->bufLen < 56) { + state->buf[state->bufLen++] = 0x00; + } + state->buf[56] = (Guchar)(state->msgLen << 3); + state->buf[57] = (Guchar)(state->msgLen >> 5); + state->buf[58] = (Guchar)(state->msgLen >> 13); + state->buf[59] = (Guchar)(state->msgLen >> 21); + state->buf[60] = (Guchar)(state->msgLen >> 29); + state->buf[61] = (Guchar)0; + state->buf[62] = (Guchar)0; + state->buf[63] = (Guchar)0; + state->bufLen = 64; + md5ProcessBlock(state); // break digest into bytes - digest[0] = (Guchar)(a & 0xff); - digest[1] = (Guchar)((a >>= 8) & 0xff); - digest[2] = (Guchar)((a >>= 8) & 0xff); - digest[3] = (Guchar)((a >>= 8) & 0xff); - digest[4] = (Guchar)(b & 0xff); - digest[5] = (Guchar)((b >>= 8) & 0xff); - digest[6] = (Guchar)((b >>= 8) & 0xff); - digest[7] = (Guchar)((b >>= 8) & 0xff); - digest[8] = (Guchar)(c & 0xff); - digest[9] = (Guchar)((c >>= 8) & 0xff); - digest[10] = (Guchar)((c >>= 8) & 0xff); - digest[11] = (Guchar)((c >>= 8) & 0xff); - digest[12] = (Guchar)(d & 0xff); - digest[13] = (Guchar)((d >>= 8) & 0xff); - digest[14] = (Guchar)((d >>= 8) & 0xff); - digest[15] = (Guchar)((d >>= 8) & 0xff); + state->digest[0] = (Guchar)state->a; + state->digest[1] = (Guchar)(state->a >> 8); + state->digest[2] = (Guchar)(state->a >> 16); + state->digest[3] = (Guchar)(state->a >> 24); + state->digest[4] = (Guchar)state->b; + state->digest[5] = (Guchar)(state->b >> 8); + state->digest[6] = (Guchar)(state->b >> 16); + state->digest[7] = (Guchar)(state->b >> 24); + state->digest[8] = (Guchar)state->c; + state->digest[9] = (Guchar)(state->c >> 8); + state->digest[10] = (Guchar)(state->c >> 16); + state->digest[11] = (Guchar)(state->c >> 24); + state->digest[12] = (Guchar)state->d; + state->digest[13] = (Guchar)(state->d >> 8); + state->digest[14] = (Guchar)(state->d >> 16); + state->digest[15] = (Guchar)(state->d >> 24); +} + +void md5(Guchar *msg, int msgLen, Guchar *digest) { + MD5State state; + int i; + + if (msgLen < 0) { + return; + } + md5Start(&state); + md5Append(&state, msg, msgLen); + md5Finish(&state); + for (i = 0; i < 16; ++i) { + digest[i] = state.digest[i]; + } } //------------------------------------------------------------------------ @@ -1042,7 +1236,7 @@ static inline Guint sha256sigma1(Guint x) { return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); } -void sha256HashBlock(Guchar *blk, Guint *H) { +static void sha256HashBlock(Guchar *blk, Guint *H) { Guint W[64]; Guint a, b, c, d, e, f, g, h; Guint T1, T2; @@ -1147,3 +1341,270 @@ static void sha256(Guchar *msg, int msgLen, Guchar *hash) { hash[i*4 + 3] = (Guchar)H[i]; } } + +//------------------------------------------------------------------------ +// SHA-384 and SHA-512 hashes +//------------------------------------------------------------------------ + +typedef unsigned long long SHA512Uint64; + +static SHA512Uint64 sha512K[80] = { + 0x428a2f98d728ae22LL, 0x7137449123ef65cdLL, + 0xb5c0fbcfec4d3b2fLL, 0xe9b5dba58189dbbcLL, + 0x3956c25bf348b538LL, 0x59f111f1b605d019LL, + 0x923f82a4af194f9bLL, 0xab1c5ed5da6d8118LL, + 0xd807aa98a3030242LL, 0x12835b0145706fbeLL, + 0x243185be4ee4b28cLL, 0x550c7dc3d5ffb4e2LL, + 0x72be5d74f27b896fLL, 0x80deb1fe3b1696b1LL, + 0x9bdc06a725c71235LL, 0xc19bf174cf692694LL, + 0xe49b69c19ef14ad2LL, 0xefbe4786384f25e3LL, + 0x0fc19dc68b8cd5b5LL, 0x240ca1cc77ac9c65LL, + 0x2de92c6f592b0275LL, 0x4a7484aa6ea6e483LL, + 0x5cb0a9dcbd41fbd4LL, 0x76f988da831153b5LL, + 0x983e5152ee66dfabLL, 0xa831c66d2db43210LL, + 0xb00327c898fb213fLL, 0xbf597fc7beef0ee4LL, + 0xc6e00bf33da88fc2LL, 0xd5a79147930aa725LL, + 0x06ca6351e003826fLL, 0x142929670a0e6e70LL, + 0x27b70a8546d22ffcLL, 0x2e1b21385c26c926LL, + 0x4d2c6dfc5ac42aedLL, 0x53380d139d95b3dfLL, + 0x650a73548baf63deLL, 0x766a0abb3c77b2a8LL, + 0x81c2c92e47edaee6LL, 0x92722c851482353bLL, + 0xa2bfe8a14cf10364LL, 0xa81a664bbc423001LL, + 0xc24b8b70d0f89791LL, 0xc76c51a30654be30LL, + 0xd192e819d6ef5218LL, 0xd69906245565a910LL, + 0xf40e35855771202aLL, 0x106aa07032bbd1b8LL, + 0x19a4c116b8d2d0c8LL, 0x1e376c085141ab53LL, + 0x2748774cdf8eeb99LL, 0x34b0bcb5e19b48a8LL, + 0x391c0cb3c5c95a63LL, 0x4ed8aa4ae3418acbLL, + 0x5b9cca4f7763e373LL, 0x682e6ff3d6b2b8a3LL, + 0x748f82ee5defb2fcLL, 0x78a5636f43172f60LL, + 0x84c87814a1f0ab72LL, 0x8cc702081a6439ecLL, + 0x90befffa23631e28LL, 0xa4506cebde82bde9LL, + 0xbef9a3f7b2c67915LL, 0xc67178f2e372532bLL, + 0xca273eceea26619cLL, 0xd186b8c721c0c207LL, + 0xeada7dd6cde0eb1eLL, 0xf57d4f7fee6ed178LL, + 0x06f067aa72176fbaLL, 0x0a637dc5a2c898a6LL, + 0x113f9804bef90daeLL, 0x1b710b35131c471bLL, + 0x28db77f523047d84LL, 0x32caab7b40c72493LL, + 0x3c9ebe0a15c9bebcLL, 0x431d67c49c100d4cLL, + 0x4cc5d4becb3e42b6LL, 0x597f299cfc657e2aLL, + 0x5fcb6fab3ad6faecLL, 0x6c44198c4a475817LL +}; + +static inline SHA512Uint64 rotr64(SHA512Uint64 x, Guint n) { + return (x >> n) | (x << (64 - n)); +} + +static inline SHA512Uint64 sha512Ch(SHA512Uint64 x, SHA512Uint64 y, + SHA512Uint64 z) { + return (x & y) ^ (~x & z); +} + +static inline SHA512Uint64 sha512Maj(SHA512Uint64 x, SHA512Uint64 y, + SHA512Uint64 z) { + return (x & y) ^ (x & z) ^ (y & z); +} + +static inline SHA512Uint64 sha512Sigma0(SHA512Uint64 x) { + return rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39); +} + +static inline SHA512Uint64 sha512Sigma1(SHA512Uint64 x) { + return rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41); +} + +static inline SHA512Uint64 sha512sigma0(SHA512Uint64 x) { + return rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7); +} + +static inline SHA512Uint64 sha512sigma1(SHA512Uint64 x) { + return rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6); +} + +static void sha512HashBlock(Guchar *blk, SHA512Uint64 *H) { + SHA512Uint64 W[80]; + SHA512Uint64 a, b, c, d, e, f, g, h; + SHA512Uint64 T1, T2; + Guint t; + + // 1. prepare the message schedule + for (t = 0; t < 16; ++t) { + W[t] = ((SHA512Uint64)blk[t*8] << 56) | + ((SHA512Uint64)blk[t*8 + 1] << 48) | + ((SHA512Uint64)blk[t*8 + 2] << 40) | + ((SHA512Uint64)blk[t*8 + 3] << 32) | + ((SHA512Uint64)blk[t*8 + 4] << 24) | + ((SHA512Uint64)blk[t*8 + 5] << 16) | + ((SHA512Uint64)blk[t*8 + 6] << 8) | + (SHA512Uint64)blk[t*8 + 7]; + } + for (t = 16; t < 80; ++t) { + W[t] = sha512sigma1(W[t-2]) + W[t-7] + sha512sigma0(W[t-15]) + W[t-16]; + } + + // 2. initialize the eight working variables + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + + // 3. + for (t = 0; t < 80; ++t) { + T1 = h + sha512Sigma1(e) + sha512Ch(e,f,g) + sha512K[t] + W[t]; + T2 = sha512Sigma0(a) + sha512Maj(a,b,c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // 4. compute the intermediate hash value + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + +static void sha512(Guchar *msg, int msgLen, Guchar *hash) { + Guchar blk[128]; + SHA512Uint64 H[8]; + int blkLen, i; + + H[0] = 0x6a09e667f3bcc908LL; + H[1] = 0xbb67ae8584caa73bLL; + H[2] = 0x3c6ef372fe94f82bLL; + H[3] = 0xa54ff53a5f1d36f1LL; + H[4] = 0x510e527fade682d1LL; + H[5] = 0x9b05688c2b3e6c1fLL; + H[6] = 0x1f83d9abfb41bd6bLL; + H[7] = 0x5be0cd19137e2179LL; + + blkLen = 0; + for (i = 0; i + 128 <= msgLen; i += 128) { + sha512HashBlock(msg + i, H); + } + blkLen = msgLen - i; + if (blkLen > 0) { + memcpy(blk, msg + i, blkLen); + } + + // pad the message + blk[blkLen++] = 0x80; + if (blkLen > 112) { + while (blkLen < 128) { + blk[blkLen++] = 0; + } + sha512HashBlock(blk, H); + blkLen = 0; + } + while (blkLen < 112) { + blk[blkLen++] = 0; + } + blk[112] = 0; + blk[113] = 0; + blk[114] = 0; + blk[115] = 0; + blk[116] = 0; + blk[117] = 0; + blk[118] = 0; + blk[119] = 0; + blk[120] = 0; + blk[121] = 0; + blk[122] = 0; + blk[123] = 0; + blk[124] = (Guchar)(msgLen >> 21); + blk[125] = (Guchar)(msgLen >> 13); + blk[126] = (Guchar)(msgLen >> 5); + blk[127] = (Guchar)(msgLen << 3); + sha512HashBlock(blk, H); + + // copy the output into the buffer (convert words to bytes) + for (i = 0; i < 8; ++i) { + hash[i*8] = (Guchar)(H[i] >> 56); + hash[i*8 + 1] = (Guchar)(H[i] >> 48); + hash[i*8 + 2] = (Guchar)(H[i] >> 40); + hash[i*8 + 3] = (Guchar)(H[i] >> 32); + hash[i*8 + 4] = (Guchar)(H[i] >> 24); + hash[i*8 + 5] = (Guchar)(H[i] >> 16); + hash[i*8 + 6] = (Guchar)(H[i] >> 8); + hash[i*8 + 7] = (Guchar)H[i]; + } +} + +static void sha384(Guchar *msg, int msgLen, Guchar *hash) { + Guchar blk[128]; + SHA512Uint64 H[8]; + int blkLen, i; + + H[0] = 0xcbbb9d5dc1059ed8LL; + H[1] = 0x629a292a367cd507LL; + H[2] = 0x9159015a3070dd17LL; + H[3] = 0x152fecd8f70e5939LL; + H[4] = 0x67332667ffc00b31LL; + H[5] = 0x8eb44a8768581511LL; + H[6] = 0xdb0c2e0d64f98fa7LL; + H[7] = 0x47b5481dbefa4fa4LL; + + blkLen = 0; + for (i = 0; i + 128 <= msgLen; i += 128) { + sha512HashBlock(msg + i, H); + } + blkLen = msgLen - i; + if (blkLen > 0) { + memcpy(blk, msg + i, blkLen); + } + + // pad the message + blk[blkLen++] = 0x80; + if (blkLen > 112) { + while (blkLen < 128) { + blk[blkLen++] = 0; + } + sha512HashBlock(blk, H); + blkLen = 0; + } + while (blkLen < 112) { + blk[blkLen++] = 0; + } + blk[112] = 0; + blk[113] = 0; + blk[114] = 0; + blk[115] = 0; + blk[116] = 0; + blk[117] = 0; + blk[118] = 0; + blk[119] = 0; + blk[120] = 0; + blk[121] = 0; + blk[122] = 0; + blk[123] = 0; + blk[124] = (Guchar)(msgLen >> 21); + blk[125] = (Guchar)(msgLen >> 13); + blk[126] = (Guchar)(msgLen >> 5); + blk[127] = (Guchar)(msgLen << 3); + sha512HashBlock(blk, H); + + // copy the output into the buffer (convert words to bytes) + for (i = 0; i < 6; ++i) { + hash[i*8] = (Guchar)(H[i] >> 56); + hash[i*8 + 1] = (Guchar)(H[i] >> 48); + hash[i*8 + 2] = (Guchar)(H[i] >> 40); + hash[i*8 + 3] = (Guchar)(H[i] >> 32); + hash[i*8 + 4] = (Guchar)(H[i] >> 24); + hash[i*8 + 5] = (Guchar)(H[i] >> 16); + hash[i*8 + 6] = (Guchar)(H[i] >> 8); + hash[i*8 + 7] = (Guchar)H[i]; + } +} diff --git a/xpdf/Decrypt.h b/xpdf/Decrypt.h index 156acec..5ddcfc8 100644 --- a/xpdf/Decrypt.h +++ b/xpdf/Decrypt.h @@ -42,6 +42,8 @@ public: private: + static void r6Hash(Guchar *key, int keyLen, const char *pwd, int pwdLen, + char *userKey); static GBool makeFileKey2(int encVersion, int encRevision, int keyLength, GString *ownerKey, GString *userKey, int permissions, GString *fileID, @@ -104,8 +106,24 @@ private: //------------------------------------------------------------------------ +struct MD5State { + Gulong a, b, c, d; + Guchar buf[64]; + int bufLen; + int msgLen; + Guchar digest[16]; +}; + extern void rc4InitKey(Guchar *key, int keyLen, Guchar *state); extern Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c); +void md5Start(MD5State *state); +void md5Append(MD5State *state, Guchar *data, int dataLen); +void md5Finish(MD5State *state); extern void md5(Guchar *msg, int msgLen, Guchar *digest); +extern void aesKeyExpansion(DecryptAESState *s, + Guchar *objKey, int objKeyLen, + GBool decrypt); +extern void aesEncryptBlock(DecryptAESState *s, Guchar *in); +extern void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last); #endif diff --git a/xpdf/Dict.cc b/xpdf/Dict.cc index 3ef804b..7e33fff 100644 --- a/xpdf/Dict.cc +++ b/xpdf/Dict.cc @@ -20,13 +20,24 @@ #include "Dict.h" //------------------------------------------------------------------------ + +struct DictEntry { + char *key; + Object val; + DictEntry *next; +}; + +//------------------------------------------------------------------------ // Dict //------------------------------------------------------------------------ Dict::Dict(XRef *xrefA) { xref = xrefA; - entries = NULL; - size = length = 0; + size = 8; + length = 0; + entries = (DictEntry *)gmallocn(size, sizeof(DictEntry)); + hashTab = (DictEntry **)gmallocn(2 * size - 1, sizeof(DictEntry *)); + memset(hashTab, 0, (2 * size - 1) * sizeof(DictEntry *)); ref = 1; } @@ -38,32 +49,69 @@ Dict::~Dict() { entries[i].val.free(); } gfree(entries); + gfree(hashTab); } void Dict::add(char *key, Object *val) { - if (length == size) { - if (length == 0) { - size = 8; - } else { - size *= 2; + DictEntry *e; + int h; + + if ((e = find(key))) { + e->val.free(); + e->val = *val; + gfree(key); + } else { + if (length == size) { + expand(); } - entries = (DictEntry *)greallocn(entries, size, sizeof(DictEntry)); + h = hash(key); + entries[length].key = key; + entries[length].val = *val; + entries[length].next = hashTab[h]; + hashTab[h] = &entries[length]; + ++length; } - entries[length].key = key; - entries[length].val = *val; - ++length; } -inline DictEntry *Dict::find(const char *key) { - int i; +void Dict::expand() { + int h, i; + size *= 2; + entries = (DictEntry *)greallocn(entries, size, sizeof(DictEntry)); + hashTab = (DictEntry **)greallocn(hashTab, 2 * size - 1, + sizeof(DictEntry *)); + memset(hashTab, 0, (2 * size - 1) * sizeof(DictEntry *)); for (i = 0; i < length; ++i) { - if (!strcmp(key, entries[i].key)) - return &entries[i]; + h = hash(entries[i].key); + entries[i].next = hashTab[h]; + hashTab[h] = &entries[i]; + } +} + +inline DictEntry *Dict::find(const char *key) { + DictEntry *e; + int h; + + h = hash(key); + for (e = hashTab[h]; e; e = e->next) { + if (!strcmp(key, e->key)) { + return e; + } } return NULL; } +int Dict::hash(const char *key) { + const char *p; + unsigned int h; + + h = 0; + for (p = key; *p; ++p) { + h = 17 * h + (int)(*p & 0xff); + } + return (int)(h % (2 * size - 1)); +} + GBool Dict::is(const char *type) { DictEntry *e; diff --git a/xpdf/Dict.h b/xpdf/Dict.h index 1f3f8b5..301d0b8 100644 --- a/xpdf/Dict.h +++ b/xpdf/Dict.h @@ -17,15 +17,12 @@ #include "Object.h" +struct DictEntry; + //------------------------------------------------------------------------ // Dict //------------------------------------------------------------------------ -struct DictEntry { - char *key; - Object val; -}; - class Dict { public: @@ -67,11 +64,14 @@ private: XRef *xref; // the xref table for this PDF file DictEntry *entries; // array of entries + DictEntry **hashTab; // hash table pointers int size; // size of <entries> array int length; // number of entries in dictionary int ref; // reference count DictEntry *find(const char *key); + void expand(); + int hash(const char *key); }; #endif diff --git a/xpdf/Error.cc b/xpdf/Error.cc index cb4f27c..600442e 100644 --- a/xpdf/Error.cc +++ b/xpdf/Error.cc @@ -2,7 +2,7 @@ // // Error.cc // -// Copyright 1996-2003 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -41,9 +41,12 @@ void setErrorCallback(void (*cbk)(void *data, ErrorCategory category, errorCbkData = data; } -void CDECL error(ErrorCategory category, int pos, const char *msg, ...) { +void CDECL error(ErrorCategory category, GFileOffset pos, + const char *msg, ...) { va_list args; - GString *s; + GString *s, *sanitized; + char c; + int i; // NB: this can be called before the globalParams object is created if (!errorCbk && globalParams && globalParams->getErrQuiet()) { @@ -52,17 +55,32 @@ void CDECL error(ErrorCategory category, int pos, const char *msg, ...) { va_start(args, msg); s = GString::formatv(msg, args); va_end(args); + + // remove non-printable characters, just in case they might cause + // problems for the terminal program + sanitized = new GString(); + for (i = 0; i < s->getLength(); ++i) { + c = s->getChar(i); + if (c >= 0x20 && c <= 0x7e) { + sanitized->append(c); + } else { + sanitized->appendf("<{0:02x}>", c & 0xff); + } + } + if (errorCbk) { - (*errorCbk)(errorCbkData, category, pos, s->getCString()); + (*errorCbk)(errorCbkData, category, (int)pos, sanitized->getCString()); } else { if (pos >= 0) { fprintf(stderr, "%s (%d): %s\n", - errorCategoryNames[category], pos, s->getCString()); + errorCategoryNames[category], (int)pos, sanitized->getCString()); } else { fprintf(stderr, "%s: %s\n", - errorCategoryNames[category], s->getCString()); + errorCategoryNames[category], sanitized->getCString()); } fflush(stderr); } + delete s; + delete sanitized; } diff --git a/xpdf/Error.h b/xpdf/Error.h index a4ae5c9..6c6afe3 100644 --- a/xpdf/Error.h +++ b/xpdf/Error.h @@ -17,11 +17,12 @@ #include <stdio.h> #include "config.h" +#include "gfile.h" enum ErrorCategory { errSyntaxWarning, // PDF syntax error which can be worked around; // output will probably be correct - errSyntaxError, // PDF syntax error which can be worked around; + errSyntaxError, // PDF syntax error which cannot be worked around; // output will probably be incorrect errConfig, // error in Xpdf config info (xpdfrc file, etc.) errCommandLine, // error in user-supplied parameters, action not @@ -37,6 +38,7 @@ extern void setErrorCallback(void (*cbk)(void *data, ErrorCategory category, int pos, char *msg), void *data); -extern void CDECL error(ErrorCategory category, int pos, const char *msg, ...); +extern void CDECL error(ErrorCategory category, GFileOffset pos, + const char *msg, ...); #endif diff --git a/xpdf/Form.cc b/xpdf/Form.cc new file mode 100644 index 0000000..f65a1e2 --- /dev/null +++ b/xpdf/Form.cc @@ -0,0 +1,67 @@ +//======================================================================== +// +// Form.cc +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include "GlobalParams.h" +#include "Error.h" +#include "Object.h" +#include "PDFDoc.h" +#include "AcroForm.h" +#include "XFAForm.h" +#include "Form.h" + +//------------------------------------------------------------------------ +// Form +//------------------------------------------------------------------------ + +Form *Form::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObj) { + Form *form; + Object xfaObj, catDict, needsRenderingObj; + + if (!acroFormObj->isDict()) { + error(errSyntaxError, -1, "AcroForm object is wrong type"); + return NULL; + } + //~ temporary: create an XFAForm only for XFAF, not for dynamic XFA + acroFormObj->dictLookup("XFA", &xfaObj); + docA->getXRef()->getCatalog(&catDict); + catDict.dictLookup("NeedsRendering", &needsRenderingObj); + catDict.free(); + if (globalParams->getEnableXFA() && + !xfaObj.isNull() && + !(needsRenderingObj.isBool() && needsRenderingObj.getBool())) { + form = XFAForm::load(docA, acroFormObj, &xfaObj); + } else { + form = AcroForm::load(docA, catalog, acroFormObj); + } + xfaObj.free(); + needsRenderingObj.free(); + return form; +} + +Form::Form(PDFDoc *docA) { + doc = docA; +} + +Form::~Form() { +} + +//------------------------------------------------------------------------ +// FormField +//------------------------------------------------------------------------ + +FormField::FormField() { +} + +FormField::~FormField() { +} diff --git a/xpdf/Form.h b/xpdf/Form.h new file mode 100644 index 0000000..50c0f47 --- /dev/null +++ b/xpdf/Form.h @@ -0,0 +1,64 @@ +//======================================================================== +// +// Form.h +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef FORM_H +#define FORM_H + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "gtypes.h" + +class Gfx; +class FormField; + +//------------------------------------------------------------------------ + +class Form { +public: + + static Form *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObj); + + virtual ~Form(); + + virtual const char *getType() = 0; + + virtual void draw(int pageNum, Gfx *gfx, GBool printing) = 0; + + virtual int getNumFields() = 0; + virtual FormField *getField(int idx) = 0; + +protected: + + Form(PDFDoc *docA); + + PDFDoc *doc; +}; + +//------------------------------------------------------------------------ + +class FormField { +public: + + FormField(); + virtual ~FormField(); + + virtual const char *getType() = 0; + virtual Unicode *getName(int *length) = 0; + virtual Unicode *getValue(int *length) = 0; + + // Return the resource dictionaries used to draw this field. The + // returned object must be either a dictionary or an array of + // dictonaries. + virtual Object *getResources(Object *res) = 0; +}; + +#endif diff --git a/xpdf/Function.cc b/xpdf/Function.cc index 2659af1..a06d10d 100644 --- a/xpdf/Function.cc +++ b/xpdf/Function.cc @@ -17,6 +17,7 @@ #include <ctype.h> #include <math.h> #include "gmem.h" +#include "GList.h" #include "Object.h" #include "Dict.h" #include "Stream.h" @@ -350,7 +351,7 @@ SampledFunction::SampledFunction(Object *funcObj, Dict *dict) { samples = (double *)gmallocn(nSamples, sizeof(double)); buf = 0; bits = 0; - bitMask = (1 << sampleBits) - 1; + bitMask = (sampleBits < 32) ? ((1 << sampleBits) - 1) : 0xffffffffU; str->reset(); for (i = 0; i < nSamples; ++i) { if (sampleBits == 8) { @@ -778,55 +779,57 @@ void StitchingFunction::transform(double *in, double *out) { // PostScriptFunction //------------------------------------------------------------------------ -enum PSOp { - psOpAbs, - psOpAdd, - psOpAnd, - psOpAtan, - psOpBitshift, - psOpCeiling, - psOpCopy, - psOpCos, - psOpCvi, - psOpCvr, - psOpDiv, - psOpDup, - psOpEq, - psOpExch, - psOpExp, - psOpFalse, - psOpFloor, - psOpGe, - psOpGt, - psOpIdiv, - psOpIndex, - psOpLe, - psOpLn, - psOpLog, - psOpLt, - psOpMod, - psOpMul, - psOpNe, - psOpNeg, - psOpNot, - psOpOr, - psOpPop, - psOpRoll, - psOpRound, - psOpSin, - psOpSqrt, - psOpSub, - psOpTrue, - psOpTruncate, - psOpXor, - psOpIf, - psOpIfelse, - psOpReturn -}; +// This is not an enum, because we can't foreward-declare the enum +// type in Function.h +#define psOpAbs 0 +#define psOpAdd 1 +#define psOpAnd 2 +#define psOpAtan 3 +#define psOpBitshift 4 +#define psOpCeiling 5 +#define psOpCopy 6 +#define psOpCos 7 +#define psOpCvi 8 +#define psOpCvr 9 +#define psOpDiv 10 +#define psOpDup 11 +#define psOpEq 12 +#define psOpExch 13 +#define psOpExp 14 +#define psOpFalse 15 +#define psOpFloor 16 +#define psOpGe 17 +#define psOpGt 18 +#define psOpIdiv 19 +#define psOpIndex 20 +#define psOpLe 21 +#define psOpLn 22 +#define psOpLog 23 +#define psOpLt 24 +#define psOpMod 25 +#define psOpMul 26 +#define psOpNe 27 +#define psOpNeg 28 +#define psOpNot 29 +#define psOpOr 30 +#define psOpPop 31 +#define psOpRoll 32 +#define psOpRound 33 +#define psOpSin 34 +#define psOpSqrt 35 +#define psOpSub 36 +#define psOpTrue 37 +#define psOpTruncate 38 +#define psOpXor 39 +#define psOpPush 40 +#define psOpJ 41 +#define psOpJz 42 + +#define nPSOps 43 // Note: 'if' and 'ifelse' are parsed separately. // The rest are listed here in alphabetical order. -// The index in this table is equivalent to the entry in PSOp. +// The index in this table is equivalent to the psOpXXX defines. static const char *psOpNames[] = { "abs", "add", @@ -870,218 +873,22 @@ static const char *psOpNames[] = { "xor" }; -#define nPSOps (sizeof(psOpNames) / sizeof(char *)) - -enum PSObjectType { - psBool, - psInt, - psReal, - psOperator, - psBlock -}; - -// In the code array, 'if'/'ifelse' operators take up three slots -// plus space for the code in the subclause(s). -// -// +---------------------------------+ -// | psOperator: psOpIf / psOpIfelse | -// +---------------------------------+ -// | psBlock: ptr=<A> | -// +---------------------------------+ -// | psBlock: ptr=<B> | -// +---------------------------------+ -// | if clause | -// | ... | -// | psOperator: psOpReturn | -// +---------------------------------+ -// <A> | else clause | -// | ... | -// | psOperator: psOpReturn | -// +---------------------------------+ -// <B> | ... | -// -// For 'if', pointer <A> is present in the code stream but unused. - -struct PSObject { - PSObjectType type; +struct PSCode { + int op; union { - GBool booln; // boolean (stack only) - int intg; // integer (stack and code) - double real; // real (stack and code) - PSOp op; // operator (code only) - int blk; // if/ifelse block pointer (code only) - }; + double d; + int i; + } val; }; #define psStackSize 100 -class PSStack { -public: - - PSStack() { sp = psStackSize; } - void pushBool(GBool booln); - void pushInt(int intg); - void pushReal(double real); - GBool popBool(); - int popInt(); - double popNum(); - GBool empty() { return sp == psStackSize; } - GBool topIsInt() { return sp < psStackSize && stack[sp].type == psInt; } - GBool topTwoAreInts() - { return sp < psStackSize - 1 && - stack[sp].type == psInt && - stack[sp+1].type == psInt; } - GBool topIsReal() { return sp < psStackSize && stack[sp].type == psReal; } - GBool topTwoAreNums() - { return sp < psStackSize - 1 && - (stack[sp].type == psInt || stack[sp].type == psReal) && - (stack[sp+1].type == psInt || stack[sp+1].type == psReal); } - void copy(int n); - void roll(int n, int j); - void index(int i); - void pop(); - -private: - - GBool checkOverflow(int n = 1); - GBool checkUnderflow(); - GBool checkType(PSObjectType t1, PSObjectType t2); - - PSObject stack[psStackSize]; - int sp; -}; - -GBool PSStack::checkOverflow(int n) { - if (sp - n < 0) { - error(errSyntaxError, -1, "Stack overflow in PostScript function"); - return gFalse; - } - return gTrue; -} - -GBool PSStack::checkUnderflow() { - if (sp == psStackSize) { - error(errSyntaxError, -1, "Stack underflow in PostScript function"); - return gFalse; - } - return gTrue; -} - -GBool PSStack::checkType(PSObjectType t1, PSObjectType t2) { - if (stack[sp].type != t1 && stack[sp].type != t2) { - error(errSyntaxError, -1, "Type mismatch in PostScript function"); - return gFalse; - } - return gTrue; -} - -void PSStack::pushBool(GBool booln) { - if (checkOverflow()) { - stack[--sp].type = psBool; - stack[sp].booln = booln; - } -} - -void PSStack::pushInt(int intg) { - if (checkOverflow()) { - stack[--sp].type = psInt; - stack[sp].intg = intg; - } -} - -void PSStack::pushReal(double real) { - if (checkOverflow()) { - stack[--sp].type = psReal; - stack[sp].real = real; - } -} - -GBool PSStack::popBool() { - if (checkUnderflow() && checkType(psBool, psBool)) { - return stack[sp++].booln; - } - return gFalse; -} - -int PSStack::popInt() { - if (checkUnderflow() && checkType(psInt, psInt)) { - return stack[sp++].intg; - } - return 0; -} - -double PSStack::popNum() { - double ret; - - if (checkUnderflow() && checkType(psInt, psReal)) { - ret = (stack[sp].type == psInt) ? (double)stack[sp].intg : stack[sp].real; - ++sp; - return ret; - } - return 0; -} - -void PSStack::copy(int n) { - int i; - - if (sp + n > psStackSize) { - error(errSyntaxError, -1, "Stack underflow in PostScript function"); - return; - } - if (!checkOverflow(n)) { - return; - } - for (i = sp + n - 1; i >= sp; --i) { - stack[i - n] = stack[i]; - } - sp -= n; -} - -void PSStack::roll(int n, int j) { - PSObject obj; - int i, k; - - if (j >= 0) { - j %= n; - } else { - j = -j % n; - if (j != 0) { - j = n - j; - } - } - if (n <= 0 || j == 0 || n > psStackSize || sp + n > psStackSize) { - return; - } - for (i = 0; i < j; ++i) { - obj = stack[sp]; - for (k = sp; k < sp + n - 1; ++k) { - stack[k] = stack[k+1]; - } - stack[sp + n - 1] = obj; - } -} - -void PSStack::index(int i) { - if (!checkOverflow()) { - return; - } - --sp; - stack[sp] = stack[sp + 1 + i]; -} - -void PSStack::pop() { - if (!checkUnderflow()) { - return; - } - ++sp; -} - PostScriptFunction::PostScriptFunction(Object *funcObj, Dict *dict) { Stream *str; - int codePtr; + GList *tokens; GString *tok; double in[funcMaxInputs]; - int i; + int tokPtr, codePtr, i; codeString = NULL; code = NULL; @@ -1104,22 +911,27 @@ PostScriptFunction::PostScriptFunction(Object *funcObj, Dict *dict) { } str = funcObj->getStream(); - //----- parse the function + //----- tokenize the function codeString = new GString(); + tokens = new GList(); str->reset(); - if (!(tok = getToken(str)) || tok->cmp("{")) { + while ((tok = getToken(str))) { + tokens->append(tok); + } + str->close(); + + //----- parse the function + if (tokens->getLength() < 1 || + ((GString *)tokens->get(0))->cmp("{")) { error(errSyntaxError, -1, "Expected '{' at start of PostScript function"); - if (tok) { - delete tok; - } - goto err1; + goto err2; } - delete tok; + tokPtr = 1; codePtr = 0; - if (!parseCode(str, &codePtr)) { + if (!parseCode(tokens, &tokPtr, &codePtr)) { goto err2; } - str->close(); + codeLen = codePtr; //----- set up the cache for (i = 0; i < m; ++i) { @@ -1131,16 +943,16 @@ PostScriptFunction::PostScriptFunction(Object *funcObj, Dict *dict) { ok = gTrue; err2: - str->close(); + deleteGList(tokens, GString); err1: return; } PostScriptFunction::PostScriptFunction(PostScriptFunction *func) { memcpy(this, func, sizeof(PostScriptFunction)); - code = (PSObject *)gmallocn(codeSize, sizeof(PSObject)); - memcpy(code, func->code, codeSize * sizeof(PSObject)); codeString = func->codeString->copy(); + code = (PSCode *)gmallocn(codeSize, sizeof(PSCode)); + memcpy(code, func->code, codeSize * sizeof(PSCode)); } PostScriptFunction::~PostScriptFunction() { @@ -1151,8 +963,9 @@ PostScriptFunction::~PostScriptFunction() { } void PostScriptFunction::transform(double *in, double *out) { - PSStack *stack; - int i; + double stack[psStackSize]; + double x; + int sp, i; // check the cache for (i = 0; i < m; ++i) { @@ -1167,25 +980,28 @@ void PostScriptFunction::transform(double *in, double *out) { return; } - stack = new PSStack(); for (i = 0; i < m; ++i) { - //~ may need to check for integers here - stack->pushReal(in[i]); + stack[psStackSize - 1 - i] = in[i]; } - exec(stack, 0); - for (i = n - 1; i >= 0; --i) { - out[i] = stack->popNum(); - if (out[i] < range[i][0]) { + sp = exec(stack, psStackSize - m); + // if (sp < psStackSize - n) { + // error(errSyntaxWarning, -1, + // "Extra values on stack at end of PostScript function"); + // } + if (sp > psStackSize - n) { + error(errSyntaxError, -1, "Stack underflow in PostScript function"); + sp = psStackSize - n; + } + for (i = 0; i < n; ++i) { + x = stack[sp + n - 1 - i]; + if (x < range[i][0]) { out[i] = range[i][0]; - } else if (out[i] > range[i][1]) { + } else if (x > range[i][1]) { out[i] = range[i][1]; + } else { + out[i] = x; } } - // if (!stack->empty()) { - // error(errSyntaxWarning, -1, - // "Extra values on stack at end of PostScript function"); - // } - delete stack; // save current result in the cache for (i = 0; i < m; ++i) { @@ -1196,101 +1012,71 @@ void PostScriptFunction::transform(double *in, double *out) { } } -GBool PostScriptFunction::parseCode(Stream *str, int *codePtr) { +GBool PostScriptFunction::parseCode(GList *tokens, int *tokPtr, int *codePtr) { GString *tok; char *p; - GBool isReal; - int opPtr, elsePtr; int a, b, mid, cmp; + int codePtr0, codePtr1; while (1) { - if (!(tok = getToken(str))) { + if (*tokPtr >= tokens->getLength()) { error(errSyntaxError, -1, "Unexpected end of PostScript function stream"); return gFalse; } + tok = (GString *)tokens->get((*tokPtr)++); p = tok->getCString(); if (isdigit(*p) || *p == '.' || *p == '-') { - isReal = gFalse; - for (; *p; ++p) { - if (*p == '.') { - isReal = gTrue; - break; - } - } - resizeCode(*codePtr); - if (isReal) { - code[*codePtr].type = psReal; - code[*codePtr].real = atof(tok->getCString()); - } else { - code[*codePtr].type = psInt; - code[*codePtr].intg = atoi(tok->getCString()); - } - ++*codePtr; - delete tok; + addCodeD(codePtr, psOpPush, atof(tok->getCString())); } else if (!tok->cmp("{")) { - delete tok; - opPtr = *codePtr; - *codePtr += 3; - resizeCode(opPtr + 2); - if (!parseCode(str, codePtr)) { + codePtr0 = *codePtr; + addCodeI(codePtr, psOpJz, 0); + if (!parseCode(tokens, tokPtr, codePtr)) { return gFalse; } - if (!(tok = getToken(str))) { + if (*tokPtr >= tokens->getLength()) { error(errSyntaxError, -1, "Unexpected end of PostScript function stream"); return gFalse; } - if (!tok->cmp("{")) { - elsePtr = *codePtr; - if (!parseCode(str, codePtr)) { + tok = (GString *)tokens->get((*tokPtr)++); + if (!tok->cmp("if")) { + code[codePtr0].val.i = *codePtr; + } else if (!tok->cmp("{")) { + codePtr1 = *codePtr; + addCodeI(codePtr, psOpJ, 0); + code[codePtr0].val.i = *codePtr; + if (!parseCode(tokens, tokPtr, codePtr)) { return gFalse; } - delete tok; - if (!(tok = getToken(str))) { + if (*tokPtr >= tokens->getLength()) { error(errSyntaxError, -1, "Unexpected end of PostScript function stream"); return gFalse; } - } else { - elsePtr = -1; - } - if (!tok->cmp("if")) { - if (elsePtr >= 0) { - error(errSyntaxError, -1, - "Got 'if' operator with two blocks in PostScript function"); - return gFalse; - } - code[opPtr].type = psOperator; - code[opPtr].op = psOpIf; - code[opPtr+2].type = psBlock; - code[opPtr+2].blk = *codePtr; - } else if (!tok->cmp("ifelse")) { - if (elsePtr < 0) { + tok = (GString *)tokens->get((*tokPtr)++); + if (!tok->cmp("ifelse")) { + code[codePtr1].val.i = *codePtr; + } else { error(errSyntaxError, -1, - "Got 'ifelse' operator with one block in PostScript function"); + "Expected 'ifelse' in PostScript function stream"); return gFalse; } - code[opPtr].type = psOperator; - code[opPtr].op = psOpIfelse; - code[opPtr+1].type = psBlock; - code[opPtr+1].blk = elsePtr; - code[opPtr+2].type = psBlock; - code[opPtr+2].blk = *codePtr; } else { error(errSyntaxError, -1, - "Expected if/ifelse operator in PostScript function"); - delete tok; + "Expected 'if' in PostScript function stream"); return gFalse; } - delete tok; } else if (!tok->cmp("}")) { - delete tok; - resizeCode(*codePtr); - code[*codePtr].type = psOperator; - code[*codePtr].op = psOpReturn; - ++*codePtr; break; + } else if (!tok->cmp("if")) { + error(errSyntaxError, -1, + "Unexpected 'if' in PostScript function stream"); + return gFalse; + } else if (!tok->cmp("ifelse")) { + error(errSyntaxError, -1, + "Unexpected 'ifelse' in PostScript function stream"); + return gFalse; } else { a = -1; b = nPSOps; @@ -1311,19 +1097,55 @@ GBool PostScriptFunction::parseCode(Stream *str, int *codePtr) { error(errSyntaxError, -1, "Unknown operator '{0:t}' in PostScript function", tok); - delete tok; return gFalse; } - delete tok; - resizeCode(*codePtr); - code[*codePtr].type = psOperator; - code[*codePtr].op = (PSOp)a; - ++*codePtr; + addCode(codePtr, a); } } return gTrue; } +void PostScriptFunction::addCode(int *codePtr, int op) { + if (*codePtr >= codeSize) { + if (codeSize) { + codeSize *= 2; + } else { + codeSize = 16; + } + code = (PSCode *)greallocn(code, codeSize, sizeof(PSCode)); + } + code[*codePtr].op = op; + ++(*codePtr); +} + +void PostScriptFunction::addCodeI(int *codePtr, int op, int x) { + if (*codePtr >= codeSize) { + if (codeSize) { + codeSize *= 2; + } else { + codeSize = 16; + } + code = (PSCode *)greallocn(code, codeSize, sizeof(PSCode)); + } + code[*codePtr].op = op; + code[*codePtr].val.i = x; + ++(*codePtr); +} + +void PostScriptFunction::addCodeD(int *codePtr, int op, double x) { + if (*codePtr >= codeSize) { + if (codeSize) { + codeSize *= 2; + } else { + codeSize = 16; + } + code = (PSCode *)greallocn(code, codeSize, sizeof(PSCode)); + } + code[*codePtr].op = op; + code[*codePtr].val.d = x; + ++(*codePtr); +} + GString *PostScriptFunction::getToken(Stream *str) { GString *s; int c; @@ -1333,7 +1155,8 @@ GString *PostScriptFunction::getToken(Stream *str) { comment = gFalse; while (1) { if ((c = str->getChar()) == EOF) { - break; + delete s; + return NULL; } codeString->append(c); if (comment) { @@ -1372,323 +1195,362 @@ GString *PostScriptFunction::getToken(Stream *str) { return s; } -void PostScriptFunction::resizeCode(int newSize) { - if (newSize >= codeSize) { - codeSize += 64; - code = (PSObject *)greallocn(code, codeSize, sizeof(PSObject)); - } -} - -void PostScriptFunction::exec(PSStack *stack, int codePtr) { - int i1, i2; - double r1, r2; - GBool b1, b2; - - while (1) { - switch (code[codePtr].type) { - case psInt: - stack->pushInt(code[codePtr++].intg); +int PostScriptFunction::exec(double *stack, int sp0) { + PSCode *c; + double tmp[psStackSize]; + double t; + int sp, ip, nn, k, i; + + sp = sp0; + ip = 0; + while (ip < codeLen) { + c = &code[ip++]; + switch(c->op) { + case psOpAbs: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = fabs(stack[sp]); break; - case psReal: - stack->pushReal(code[codePtr++].real); + case psOpAdd: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] + stack[sp]; + ++sp; break; - case psOperator: - switch (code[codePtr++].op) { - case psOpAbs: - if (stack->topIsInt()) { - stack->pushInt(abs(stack->popInt())); - } else { - stack->pushReal(fabs(stack->popNum())); - } - break; - case psOpAdd: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 + i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 + r2); - } - break; - case psOpAnd: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 & i2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 && b2); - } - break; - case psOpAtan: - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(atan2(r1, r2)); - break; - case psOpBitshift: - i2 = stack->popInt(); - i1 = stack->popInt(); - if (i2 > 0) { - stack->pushInt(i1 << i2); - } else if (i2 < 0) { - stack->pushInt((int)((Guint)i1 >> -i2)); - } else { - stack->pushInt(i1); - } - break; - case psOpCeiling: - if (!stack->topIsInt()) { - stack->pushReal(ceil(stack->popNum())); - } - break; - case psOpCopy: - stack->copy(stack->popInt()); - break; - case psOpCos: - stack->pushReal(cos(stack->popNum())); - break; - case psOpCvi: - if (!stack->topIsInt()) { - stack->pushInt((int)stack->popNum()); - } - break; - case psOpCvr: - if (!stack->topIsReal()) { - stack->pushReal(stack->popNum()); - } - break; - case psOpDiv: - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 / r2); - break; - case psOpDup: - stack->copy(1); - break; - case psOpEq: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 == i2); - } else if (stack->topTwoAreNums()) { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 == r2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 == b2); - } - break; - case psOpExch: - stack->roll(2, 1); - break; - case psOpExp: - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(pow(r1, r2)); - break; - case psOpFalse: - stack->pushBool(gFalse); - break; - case psOpFloor: - if (!stack->topIsInt()) { - stack->pushReal(floor(stack->popNum())); - } - break; - case psOpGe: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 >= i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 >= r2); - } - break; - case psOpGt: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 > i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 > r2); - } - break; - case psOpIdiv: - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 / i2); - break; - case psOpIndex: - stack->index(stack->popInt()); - break; - case psOpLe: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 <= i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 <= r2); - } - break; - case psOpLn: - stack->pushReal(log(stack->popNum())); - break; - case psOpLog: - stack->pushReal(log10(stack->popNum())); - break; - case psOpLt: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 < i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 < r2); - } - break; - case psOpMod: - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 % i2); - break; - case psOpMul: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - //~ should check for out-of-range, and push a real instead - stack->pushInt(i1 * i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 * r2); - } - break; - case psOpNe: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 != i2); - } else if (stack->topTwoAreNums()) { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 != r2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 != b2); - } - break; - case psOpNeg: - if (stack->topIsInt()) { - stack->pushInt(-stack->popInt()); - } else { - stack->pushReal(-stack->popNum()); - } - break; - case psOpNot: - if (stack->topIsInt()) { - stack->pushInt(~stack->popInt()); - } else { - stack->pushBool(!stack->popBool()); - } - break; - case psOpOr: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 | i2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 || b2); - } - break; - case psOpPop: - stack->pop(); - break; - case psOpRoll: - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->roll(i1, i2); - break; - case psOpRound: - if (!stack->topIsInt()) { - r1 = stack->popNum(); - stack->pushReal((r1 >= 0) ? floor(r1 + 0.5) : ceil(r1 - 0.5)); - } - break; - case psOpSin: - stack->pushReal(sin(stack->popNum())); - break; - case psOpSqrt: - stack->pushReal(sqrt(stack->popNum())); - break; - case psOpSub: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 - i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 - r2); - } - break; - case psOpTrue: - stack->pushBool(gTrue); - break; - case psOpTruncate: - if (!stack->topIsInt()) { - r1 = stack->popNum(); - stack->pushReal((r1 >= 0) ? floor(r1) : ceil(r1)); - } - break; - case psOpXor: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 ^ i2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 ^ b2); - } - break; - case psOpIf: - b1 = stack->popBool(); - if (b1) { - exec(stack, codePtr + 2); - } - codePtr = code[codePtr + 1].blk; - break; - case psOpIfelse: - b1 = stack->popBool(); - if (b1) { - exec(stack, codePtr + 2); - } else { - exec(stack, code[codePtr].blk); + case psOpAnd: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] & (int)stack[sp]; + ++sp; + break; + case psOpAtan: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = atan2(stack[sp + 1], stack[sp]); + ++sp; + break; + case psOpBitshift: + if (sp + 1 >= psStackSize) { + goto underflow; + } + k = (int)stack[sp + 1]; + nn = (int)stack[sp]; + if (nn > 0) { + stack[sp + 1] = k << nn; + } else if (nn < 0) { + stack[sp + 1] = k >> -nn; + } else { + stack[sp + 1] = k; + } + ++sp; + break; + case psOpCeiling: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = ceil(stack[sp]); + break; + case psOpCopy: + if (sp >= psStackSize) { + goto underflow; + } + nn = (int)stack[sp++]; + if (nn < 0) { + goto invalidArg; + } + if (sp + nn > psStackSize) { + goto underflow; + } + if (sp - nn < 0) { + goto overflow; + } + for (i = 0; i < nn; ++i) { + stack[sp - nn + i] = stack[sp + i]; + } + sp -= nn; + break; + case psOpCos: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = cos(stack[sp]); + break; + case psOpCvi: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = (int)stack[sp]; + break; + case psOpCvr: + if (sp >= psStackSize) { + goto underflow; + } + break; + case psOpDiv: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] / stack[sp]; + ++sp; + break; + case psOpDup: + if (sp >= psStackSize) { + goto underflow; + } + if (sp < 1) { + goto overflow; + } + stack[sp - 1] = stack[sp]; + --sp; + break; + case psOpEq: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] == stack[sp] ? 1 : 0; + ++sp; + break; + case psOpExch: + if (sp + 1 >= psStackSize) { + goto underflow; + } + t = stack[sp]; + stack[sp] = stack[sp + 1]; + stack[sp + 1] = t; + break; + case psOpExp: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = pow(stack[sp + 1], stack[sp]); + ++sp; + break; + case psOpFalse: + if (sp < 1) { + goto overflow; + } + stack[sp - 1] = 0; + --sp; + break; + case psOpFloor: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = floor(stack[sp]); + break; + case psOpGe: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] >= stack[sp] ? 1 : 0; + ++sp; + break; + case psOpGt: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] > stack[sp] ? 1 : 0; + ++sp; + break; + case psOpIdiv: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] / (int)stack[sp]; + ++sp; + break; + case psOpIndex: + if (sp >= psStackSize) { + goto underflow; + } + k = (int)stack[sp]; + if (k < 0) { + goto invalidArg; + } + if (sp + 1 + k >= psStackSize) { + goto underflow; + } + stack[sp] = stack[sp + 1 + k]; + break; + case psOpLe: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] <= stack[sp] ? 1 : 0; + ++sp; + break; + case psOpLn: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = log(stack[sp]); + break; + case psOpLog: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = log10(stack[sp]); + break; + case psOpLt: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] < stack[sp] ? 1 : 0; + ++sp; + break; + case psOpMod: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] % (int)stack[sp]; + ++sp; + break; + case psOpMul: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] * stack[sp]; + ++sp; + break; + case psOpNe: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] != stack[sp] ? 1 : 0; + ++sp; + break; + case psOpNeg: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = -stack[sp]; + break; + case psOpNot: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = stack[sp] == 0 ? 1 : 0; + break; + case psOpOr: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] | (int)stack[sp]; + ++sp; + break; + case psOpPop: + if (sp >= psStackSize) { + goto underflow; + } + ++sp; + break; + case psOpRoll: + if (sp + 1 >= psStackSize) { + goto underflow; + } + k = (int)stack[sp++]; + nn = (int)stack[sp++]; + if (nn < 0) { + goto invalidArg; + } + if (sp + nn > psStackSize) { + goto underflow; + } + if (k >= 0) { + k %= nn; + } else { + k = -k % nn; + if (k) { + k = nn - k; } - codePtr = code[codePtr + 1].blk; - break; - case psOpReturn: - return; + } + for (i = 0; i < nn; ++i) { + tmp[i] = stack[sp + i]; + } + for (i = 0; i < nn; ++i) { + stack[sp + i] = tmp[(i + k) % nn]; } break; - default: - error(errSyntaxError, -1, - "Internal: bad object in PostScript function code"); + case psOpRound: + if (sp >= psStackSize) { + goto underflow; + } + t = stack[sp]; + stack[sp] = (t >= 0) ? floor(t + 0.5) : ceil(t - 0.5); + break; + case psOpSin: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = sin(stack[sp]); + break; + case psOpSqrt: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = sqrt(stack[sp]); + break; + case psOpSub: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] - stack[sp]; + ++sp; + break; + case psOpTrue: + if (sp < 1) { + goto overflow; + } + stack[sp - 1] = 1; + --sp; + break; + case psOpTruncate: + if (sp >= psStackSize) { + goto underflow; + } + t = stack[sp]; + stack[sp] = (t >= 0) ? floor(t) : ceil(t); + break; + case psOpXor: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] ^ (int)stack[sp]; + ++sp; + break; + case psOpPush: + if (sp < 1) { + goto overflow; + } + stack[--sp] = c->val.d; + break; + case psOpJ: + ip = c->val.i; + break; + case psOpJz: + if (sp >= psStackSize) { + goto underflow; + } + k = (int)stack[sp++]; + if (k == 0) { + ip = c->val.i; + } break; } } + return sp; + + underflow: + error(errSyntaxError, -1, "Stack underflow in PostScript function"); + return sp; + overflow: + error(errSyntaxError, -1, "Stack overflow in PostScript function"); + return sp; + invalidArg: + error(errSyntaxError, -1, "Invalid arg in PostScript function"); + return sp; } diff --git a/xpdf/Function.h b/xpdf/Function.h index 8852dca..5cf4516 100644 --- a/xpdf/Function.h +++ b/xpdf/Function.h @@ -18,10 +18,10 @@ #include "gtypes.h" #include "Object.h" +class GList; class Dict; class Stream; -struct PSObject; -class PSStack; +struct PSCode; //------------------------------------------------------------------------ // Function @@ -217,13 +217,16 @@ public: private: PostScriptFunction(PostScriptFunction *func); - GBool parseCode(Stream *str, int *codePtr); + GBool parseCode(GList *tokens, int *tokPtr, int *codePtr); + void addCode(int *codePtr, int op); + void addCodeI(int *codePtr, int op, int x); + void addCodeD(int *codePtr, int op, double x); GString *getToken(Stream *str); - void resizeCode(int newSize); - void exec(PSStack *stack, int codePtr); + int exec(double *stack, int sp0); GString *codeString; - PSObject *code; + PSCode *code; + int codeLen; int codeSize; double cacheIn[funcMaxInputs]; double cacheOut[funcMaxOutputs]; diff --git a/xpdf/Gfx.cc b/xpdf/Gfx.cc index 1979f84..0fa48f8 100644 --- a/xpdf/Gfx.cc +++ b/xpdf/Gfx.cc @@ -2,7 +2,7 @@ // // Gfx.cc // -// Copyright 1996-2003 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -36,7 +36,7 @@ #include "Annot.h" #include "OptionalContent.h" #include "Error.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "Gfx.h" // the MSVC math.h doesn't define this @@ -80,11 +80,15 @@ // fill. #define patchColorDelta (dblToCol(1 / 256.0)) +// Max errors (undefined operator, wrong number of args) allowed before +// giving up on a content stream. +#define contentStreamErrorLimit 500 + //------------------------------------------------------------------------ // Operator table //------------------------------------------------------------------------ -#ifdef WIN32 // this works around a bug in the VC7 compiler +#ifdef _WIN32 // this works around a bug in the VC7 compiler # pragma optimize("",off) #endif @@ -257,7 +261,7 @@ Operator Gfx::opTab[] = { &Gfx::opCurveTo2}, }; -#ifdef WIN32 // this works around a bug in the VC7 compiler +#ifdef _WIN32 // this works around a bug in the VC7 compiler # pragma optimize("",on) #endif @@ -337,15 +341,31 @@ GfxFont *GfxResources::lookupFont(char *name) { for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->fonts) { - if ((font = resPtr->fonts->lookup(name))) + if ((font = resPtr->fonts->lookup(name))) { return font; + } } } error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name); return NULL; } -GBool GfxResources::lookupXObject(char *name, Object *obj) { +GfxFont *GfxResources::lookupFontByRef(Ref ref) { + GfxFont *font; + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->fonts) { + if ((font = resPtr->fonts->lookupByRef(ref))) { + return font; + } + } + } + error(errSyntaxError, -1, "Unknown font ref {0:d}.{1:d}", ref.num, ref.gen); + return NULL; +} + +GBool GfxResources::lookupXObject(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -359,7 +379,7 @@ GBool GfxResources::lookupXObject(char *name, Object *obj) { return gFalse; } -GBool GfxResources::lookupXObjectNF(char *name, Object *obj) { +GBool GfxResources::lookupXObjectNF(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -373,9 +393,16 @@ GBool GfxResources::lookupXObjectNF(char *name, Object *obj) { return gFalse; } -void GfxResources::lookupColorSpace(char *name, Object *obj) { +void GfxResources::lookupColorSpace(const char *name, Object *obj) { GfxResources *resPtr; + //~ should also test for G, RGB, and CMYK - but only in inline images (?) + if (!strcmp(name, "DeviceGray") || + !strcmp(name, "DeviceRGB") || + !strcmp(name, "DeviceCMYK")) { + obj->initNull(); + return; + } for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->colorSpaceDict.isDict()) { if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) { @@ -387,15 +414,19 @@ void GfxResources::lookupColorSpace(char *name, Object *obj) { obj->initNull(); } -GfxPattern *GfxResources::lookupPattern(char *name) { +GfxPattern *GfxResources::lookupPattern(const char *name + ) { GfxResources *resPtr; GfxPattern *pattern; - Object obj; + Object objRef, obj; for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->patternDict.isDict()) { if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) { - pattern = GfxPattern::parse(&obj); + resPtr->patternDict.dictLookupNF(name, &objRef); + pattern = GfxPattern::parse(&objRef, &obj + ); + objRef.free(); obj.free(); return pattern; } @@ -406,7 +437,8 @@ GfxPattern *GfxResources::lookupPattern(char *name) { return NULL; } -GfxShading *GfxResources::lookupShading(char *name) { +GfxShading *GfxResources::lookupShading(const char *name + ) { GfxResources *resPtr; GfxShading *shading; Object obj; @@ -414,7 +446,8 @@ GfxShading *GfxResources::lookupShading(char *name) { for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->shadingDict.isDict()) { if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) { - shading = GfxShading::parse(&obj); + shading = GfxShading::parse(&obj + ); obj.free(); return shading; } @@ -425,7 +458,7 @@ GfxShading *GfxResources::lookupShading(char *name) { return NULL; } -GBool GfxResources::lookupGState(char *name, Object *obj) { +GBool GfxResources::lookupGState(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -440,7 +473,7 @@ GBool GfxResources::lookupGState(char *name, Object *obj) { return gFalse; } -GBool GfxResources::lookupPropertiesNF(char *name, Object *obj) { +GBool GfxResources::lookupPropertiesNF(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -491,6 +524,7 @@ Gfx::Gfx(PDFDoc *docA, OutputDev *outA, int pageNum, Dict *resDict, markedContentStack = new GList(); ocState = gTrue; parser = NULL; + contentStreamStack = new GList(); abortCheckCbk = abortCheckCbkA; abortCheckCbkData = abortCheckCbkDataA; @@ -535,6 +569,7 @@ Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict, markedContentStack = new GList(); ocState = gTrue; parser = NULL; + contentStreamStack = new GList(); abortCheckCbk = abortCheckCbkA; abortCheckCbkData = abortCheckCbkDataA; @@ -563,41 +598,99 @@ Gfx::~Gfx() { popResources(); } deleteGList(markedContentStack, GfxMarkedContent); + delete contentStreamStack; } -void Gfx::display(Object *obj, GBool topLevel) { - Object obj2; +void Gfx::display(Object *objRef, GBool topLevel) { + Object obj1, obj2; int i; - if (obj->isArray()) { - for (i = 0; i < obj->arrayGetLength(); ++i) { - obj->arrayGet(i, &obj2); + objRef->fetch(xref, &obj1); + if (obj1.isArray()) { + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGetNF(i, &obj2); + if (checkForContentStreamLoop(&obj2)) { + obj2.free(); + obj1.free(); + return; + } + obj2.free(); + } + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGet(i, &obj2); if (!obj2.isStream()) { - error(errSyntaxError, -1, "Weird page contents"); + error(errSyntaxError, -1, "Invalid object type for content stream"); obj2.free(); + obj1.free(); return; } obj2.free(); } - } else if (!obj->isStream()) { - error(errSyntaxError, -1, "Weird page contents"); + contentStreamStack->append(&obj1); + } else if (obj1.isStream()) { + if (checkForContentStreamLoop(objRef)) { + obj1.free(); + return; + } + contentStreamStack->append(objRef); + } else { + error(errSyntaxError, -1, "Invalid object type for content stream"); + obj1.free(); return; } - parser = new Parser(xref, new Lexer(xref, obj), gFalse); + parser = new Parser(xref, new Lexer(xref, &obj1), gFalse); go(topLevel); delete parser; parser = NULL; + contentStreamStack->del(contentStreamStack->getLength() - 1); + obj1.free(); +} + +// If <ref> is already on contentStreamStack, i.e., if there is a loop +// in the content streams, report an error, and return true. +GBool Gfx::checkForContentStreamLoop(Object *ref) { + Object *objPtr; + Object obj1; + int i, j; + + if (ref->isRef()) { + for (i = 0; i < contentStreamStack->getLength(); ++i) { + objPtr = (Object *)contentStreamStack->get(i); + if (objPtr->isRef()) { + if (ref->getRefNum() == objPtr->getRefNum() && + ref->getRefGen() == objPtr->getRefGen()) { + error(errSyntaxError, -1, "Loop in content streams"); + return gTrue; + } + } else if (objPtr->isArray()) { + for (j = 0; j < objPtr->arrayGetLength(); ++j) { + objPtr->arrayGetNF(j, &obj1); + if (obj1.isRef()) { + if (ref->getRefNum() == obj1.getRefNum() && + ref->getRefGen() == obj1.getRefGen()) { + error(errSyntaxError, -1, "Loop in content streams"); + obj1.free(); + return gTrue; + } + } + obj1.free(); + } + } + } + } + return gFalse; } void Gfx::go(GBool topLevel) { Object obj; Object args[maxArgs]; int numArgs, i; - int lastAbortCheck; + int lastAbortCheck, errCount; // scan a sequence of objects updateLevel = 1; // make sure even empty pages trigger a call to dump() lastAbortCheck = 0; + errCount = 0; numArgs = 0; parser->getObj(&obj); while (!obj.isEOF()) { @@ -613,7 +706,9 @@ void Gfx::go(GBool topLevel) { printf("\n"); fflush(stdout); } - execOp(&obj, args, numArgs); + if (!execOp(&obj, args, numArgs)) { + ++errCount; + } obj.free(); for (i = 0; i < numArgs; ++i) args[i].free(); @@ -635,6 +730,13 @@ void Gfx::go(GBool topLevel) { } } + // check for too many errors + if (errCount > contentStreamErrorLimit) { + error(errSyntaxError, -1, + "Too many errors - giving up on this content stream"); + break; + } + // got an argument - save it } else if (numArgs < maxArgs) { args[numArgs++] = obj; @@ -678,7 +780,8 @@ void Gfx::go(GBool topLevel) { } } -void Gfx::execOp(Object *cmd, Object args[], int numArgs) { +// Returns true if successful, false on error. +GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) { Operator *op; char *name; Object *argPtr; @@ -687,9 +790,11 @@ void Gfx::execOp(Object *cmd, Object args[], int numArgs) { // find operator name = cmd->getCmd(); if (!(op = findOp(name))) { - if (ignoreUndef == 0) - error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); - return; + if (ignoreUndef > 0) { + return gTrue; + } + error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); + return gFalse; } // type check args @@ -698,7 +803,7 @@ void Gfx::execOp(Object *cmd, Object args[], int numArgs) { if (numArgs < op->numArgs) { error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:s}' operator", numArgs, name); - return; + return gFalse; } if (numArgs > op->numArgs) { #if 0 @@ -713,7 +818,7 @@ void Gfx::execOp(Object *cmd, Object args[], int numArgs) { error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); - return; + return gFalse; } } for (i = 0; i < numArgs; ++i) { @@ -721,12 +826,14 @@ void Gfx::execOp(Object *cmd, Object args[], int numArgs) { error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", i, name, argPtr[i].getTypeName()); - return; + return gFalse; } } // do it (this->*op->func)(argPtr, numArgs); + + return gTrue; } Operator *Gfx::findOp(char *name) { @@ -766,7 +873,7 @@ GBool Gfx::checkArg(Object *arg, TchkType type) { return gFalse; } -int Gfx::getPos() { +GFileOffset Gfx::getPos() { return parser ? parser->getPos() : -1; } @@ -840,7 +947,7 @@ void Gfx::opSetLineWidth(Object args[], int numArgs) { } void Gfx::opSetExtGState(Object args[], int numArgs) { - Object obj1, obj2, obj3, obj4, obj5; + Object obj1, obj2, obj3, objRef3, obj4, obj5; Object args2[2]; GfxBlendMode mode; GBool haveFillOP; @@ -895,22 +1002,21 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { args2[1].free(); } obj2.free(); -#if 0 //~ need to add a new version of GfxResources::lookupFont() that - //~ takes an indirect ref instead of a name + if (obj1.dictLookup("FL", &obj2)->isNum()) { + opSetFlat(&obj2, 1); + } + obj2.free(); + + // font if (obj1.dictLookup("Font", &obj2)->isArray() && obj2.arrayGetLength() == 2) { - obj2.arrayGet(0, &args2[0]); - obj2.arrayGet(1, &args2[1]); - if (args2[0].isDict() && args2[1].isNum()) { - opSetFont(args2, 2); + obj2.arrayGetNF(0, &obj3); + obj2.arrayGetNF(1, &obj4); + if (obj3.isRef() && obj4.isNum()) { + doSetFont(res->lookupFontByRef(obj3.getRef()), obj4.getNum()); } - args2[0].free(); - args2[1].free(); - } - obj2.free(); -#endif - if (obj1.dictLookup("FL", &obj2)->isNum()) { - opSetFlat(&obj2, 1); + obj3.free(); + obj4.free(); } obj2.free(); @@ -1045,7 +1151,8 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { blendingColorSpace = NULL; isolated = knockout = gFalse; if (!obj4.dictLookup("CS", &obj5)->isNull()) { - blendingColorSpace = GfxColorSpace::parse(&obj5); + blendingColorSpace = GfxColorSpace::parse(&obj5 + ); } obj5.free(); if (obj4.dictLookup("I", &obj5)->isBool()) { @@ -1066,8 +1173,10 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { } } } - doSoftMask(&obj3, alpha, blendingColorSpace, + obj2.dictLookupNF("G", &objRef3); + doSoftMask(&obj3, &objRef3, alpha, blendingColorSpace, isolated, knockout, funcs[0], &backdropColor); + objRef3.free(); if (funcs[0]) { delete funcs[0]; } @@ -1090,7 +1199,7 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { obj1.free(); } -void Gfx::doSoftMask(Object *str, GBool alpha, +void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, Function *transferFunc, GfxColor *backdropColor) { @@ -1149,7 +1258,7 @@ void Gfx::doSoftMask(Object *str, GBool alpha, // draw it ++formDepth; - drawForm(str, resDict, m, bbox, gTrue, gTrue, + drawForm(strRef, resDict, m, bbox, gTrue, gTrue, blendingColorSpace, isolated, knockout, alpha, transferFunc, backdropColor); --formDepth; @@ -1171,7 +1280,7 @@ void Gfx::opSetFillGray(Object args[], int numArgs) { GfxColor color; state->setFillPattern(NULL); - state->setFillColorSpace(new GfxDeviceGrayColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateFillColorSpace(state); color.c[0] = dblToCol(args[0].getNum()); state->setFillColor(&color); @@ -1182,7 +1291,7 @@ void Gfx::opSetStrokeGray(Object args[], int numArgs) { GfxColor color; state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateStrokeColorSpace(state); color.c[0] = dblToCol(args[0].getNum()); state->setStrokeColor(&color); @@ -1194,7 +1303,7 @@ void Gfx::opSetFillCMYKColor(Object args[], int numArgs) { int i; state->setFillPattern(NULL); - state->setFillColorSpace(new GfxDeviceCMYKColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceCMYK)); out->updateFillColorSpace(state); for (i = 0; i < 4; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1208,7 +1317,7 @@ void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) { int i; state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK)); out->updateStrokeColorSpace(state); for (i = 0; i < 4; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1222,7 +1331,7 @@ void Gfx::opSetFillRGBColor(Object args[], int numArgs) { int i; state->setFillPattern(NULL); - state->setFillColorSpace(new GfxDeviceRGBColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceRGB)); out->updateFillColorSpace(state); for (i = 0; i < 3; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1236,7 +1345,7 @@ void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) { int i; state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB)); out->updateStrokeColorSpace(state); for (i = 0; i < 3; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1253,9 +1362,11 @@ void Gfx::opSetFillColorSpace(Object args[], int numArgs) { state->setFillPattern(NULL); res->lookupColorSpace(args[0].getName(), &obj); if (obj.isNull()) { - colorSpace = GfxColorSpace::parse(&args[0]); + colorSpace = GfxColorSpace::parse(&args[0] + ); } else { - colorSpace = GfxColorSpace::parse(&obj); + colorSpace = GfxColorSpace::parse(&obj + ); } obj.free(); if (colorSpace) { @@ -1277,9 +1388,11 @@ void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) { state->setStrokePattern(NULL); res->lookupColorSpace(args[0].getName(), &obj); if (obj.isNull()) { - colorSpace = GfxColorSpace::parse(&args[0]); + colorSpace = GfxColorSpace::parse(&args[0] + ); } else { - colorSpace = GfxColorSpace::parse(&obj); + colorSpace = GfxColorSpace::parse(&obj + ); } obj.free(); if (colorSpace) { @@ -1350,7 +1463,8 @@ void Gfx::opSetFillColorN(Object args[], int numArgs) { out->updateFillColor(state); } if (args[numArgs-1].isName() && - (pattern = res->lookupPattern(args[numArgs-1].getName()))) { + (pattern = res->lookupPattern(args[numArgs-1].getName() + ))) { state->setFillPattern(pattern); } @@ -1395,7 +1509,8 @@ void Gfx::opSetStrokeColorN(Object args[], int numArgs) { out->updateStrokeColor(state); } if (args[numArgs-1].isName() && - (pattern = res->lookupPattern(args[numArgs-1].getName()))) { + (pattern = res->lookupPattern(args[numArgs-1].getName() + ))) { state->setStrokePattern(pattern); } @@ -1751,11 +1866,11 @@ void Gfx::doPatternText() { } void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height, - GBool invert, GBool inlineImg) { + GBool invert, GBool inlineImg, GBool interpolate) { saveState(); out->setSoftMaskFromImageMask(state, ref, str, - width, height, invert, inlineImg); + width, height, invert, inlineImg, interpolate); state->clearPath(); state->moveTo(0, 0); @@ -1773,11 +1888,11 @@ void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, GfxPatternColorSpace *patCS; GfxColorSpace *cs; GfxState *savedState; - double xMin, yMin, xMax, yMax, x, y, x1, y1; + double xMin, yMin, xMax, yMax, x, y, x1, y1, t; double cxMin, cyMin, cxMax, cyMax; int xi0, yi0, xi1, yi1, xi, yi; double *ctm, *btm, *ptm; - double m[6], ictm[6], m1[6], imb[6]; + double bbox[4], m[6], ictm[6], m1[6], imb[6]; double det; double xstep, ystep; int i; @@ -1791,7 +1906,12 @@ void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, btm = baseMatrix; ptm = tPat->getMatrix(); // iCTM = invert CTM - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + if (fabs(det) < 0.000001) { + error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill"); + return; + } + det = 1 / det; ictm[0] = ctm[3] * det; ictm[1] = -ctm[1] * det; ictm[2] = -ctm[2] * det; @@ -1814,7 +1934,12 @@ void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; // construct a (device space) -> (pattern space) transform matrix - det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]); + det = m1[0] * m1[3] - m1[1] * m1[2]; + if (fabs(det) < 0.000001) { + error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill"); + return; + } + det = 1 / det; imb[0] = m1[3] * det; imb[1] = -m1[1] * det; imb[2] = -m1[2] * det; @@ -1839,9 +1964,9 @@ void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, out->updateFillColor(state); out->updateStrokeColor(state); } else { - state->setFillColorSpace(new GfxDeviceGrayColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateFillColorSpace(state); - state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateStrokeColorSpace(state); } if (!stroke) { @@ -1912,21 +2037,31 @@ void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, // draw the pattern //~ this should treat negative steps differently -- start at right/top //~ edge instead of left/bottom (?) + bbox[0] = tPat->getBBox()[0]; + bbox[1] = tPat->getBBox()[1]; + bbox[2] = tPat->getBBox()[2]; + bbox[3] = tPat->getBBox()[3]; + if (bbox[0] > bbox[2]) { + t = bbox[0]; bbox[0] = bbox[2]; bbox[2] = t; + } + if (bbox[1] > bbox[3]) { + t = bbox[1]; bbox[1] = bbox[3]; bbox[3] = t; + } xstep = fabs(tPat->getXStep()); ystep = fabs(tPat->getYStep()); - xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep); - xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1; - yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep); - yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1; + xi0 = (int)ceil((xMin - bbox[2]) / xstep); + xi1 = (int)floor((xMax - bbox[0]) / xstep) + 1; + yi0 = (int)ceil((yMin - bbox[3]) / ystep); + yi1 = (int)floor((yMax - bbox[1]) / ystep) + 1; for (i = 0; i < 4; ++i) { m1[i] = m[i]; } if (out->useTilingPatternFill()) { m1[4] = m[4]; m1[5] = m[5]; - out->tilingPatternFill(state, this, tPat->getContentStream(), + out->tilingPatternFill(state, this, tPat->getContentStreamRef(), tPat->getPaintType(), tPat->getResDict(), - m1, tPat->getBBox(), + m1, bbox, xi0, yi0, xi1, yi1, xstep, ystep); } else { for (yi = yi0; yi < yi1; ++yi) { @@ -1935,8 +2070,8 @@ void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, y = yi * ystep; m1[4] = x * m[0] + y * m[2] + m[4]; m1[5] = x * m[1] + y * m[3] + m[5]; - drawForm(tPat->getContentStream(), tPat->getResDict(), - m1, tPat->getBBox()); + drawForm(tPat->getContentStreamRef(), tPat->getResDict(), + m1, bbox); } } } @@ -1979,7 +2114,12 @@ void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, btm = baseMatrix; ptm = sPat->getMatrix(); // iCTM = invert CTM - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + if (fabs(det) < 0.000001) { + error(errSyntaxError, getPos(), "Singular matrix in shading pattern fill"); + return; + } + det = 1 / det; ictm[0] = ctm[3] * det; ictm[1] = -ctm[1] * det; ictm[2] = -ctm[2] * det; @@ -2074,11 +2214,16 @@ void Gfx::opShFill(Object args[], int numArgs) { GfxState *savedState; double xMin, yMin, xMax, yMax; + if (!out->needNonText()) { + return; + } + if (!ocState) { return; } - if (!(shading = res->lookupShading(args[0].getName()))) { + if (!(shading = res->lookupShading(args[0].getName() + ))) { return; } @@ -2487,7 +2632,7 @@ void Gfx::doRadialShFill(GfxRadialShading *shading) { GfxColor colorA, colorB; double xa, ya, xb, yb, ra, rb; double ta, tb, sa, sb; - double sz, sMin, sMax, h; + double sMin, sMax, h; double sLeft, sRight, sTop, sBottom, sZero, sDiag; GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; GBool haveSMin, haveSMax; @@ -2513,18 +2658,14 @@ void Gfx::doRadialShFill(GfxRadialShading *shading) { if (h == 0) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else if (r1 - r0 == 0) { enclosed = gFalse; theta = 0; - sz = 0; // make gcc happy - } else if (fabs(r1 - r0) >= h) { + } else if (fabs(r1 - r0) >= h - 0.0001) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else { enclosed = gFalse; - sz = -r0 / (r1 - r0); theta = asin((r1 - r0) / h); } if (enclosed) { @@ -2598,7 +2739,7 @@ void Gfx::doRadialShFill(GfxRadialShading *shading) { haveSMin = gTrue; } } - if (haveSZero && sZero < 0) { + if (haveSZero && sZero <= 0) { if (!haveSMin || sZero > sMin) { sMin = sZero; } @@ -2865,34 +3006,56 @@ void Gfx::doRadialShFill(GfxRadialShading *shading) { void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) { double x0, y0, x1, y1, x2, y2; - GfxColor color0, color1, color2; + double color0[gfxColorMaxComps]; + double color1[gfxColorMaxComps]; + double color2[gfxColorMaxComps]; int i; for (i = 0; i < shading->getNTriangles(); ++i) { - shading->getTriangle(i, &x0, &y0, &color0, - &x1, &y1, &color1, - &x2, &y2, &color2); - gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, - shading->getColorSpace()->getNComps(), 0); + shading->getTriangle(i, &x0, &y0, color0, + &x1, &y1, color1, + &x2, &y2, color2); + gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, + shading, 0); } } -void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0, - double x1, double y1, GfxColor *color1, - double x2, double y2, GfxColor *color2, - int nComps, int depth) { +void Gfx::gouraudFillTriangle(double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading, int depth) { + double dx0, dy0, dx1, dy1, dx2, dy2; double x01, y01, x12, y12, x20, y20; - GfxColor color01, color12, color20; - int i; - + double color01[gfxColorMaxComps]; + double color12[gfxColorMaxComps]; + double color20[gfxColorMaxComps]; + GfxColor c0, c1, c2; + int nComps, i; + + // recursion ends when: + // (1) color difference is smaller than gouraudColorDelta; or + // (2) triangles are smaller than 0.5 pixel (note that "device + // space" is 72dpi when generating PostScript); or + // (3) max recursion depth (gouraudMaxDepth) is hit. + nComps = shading->getColorSpace()->getNComps(); + shading->getColor(color0, &c0); + shading->getColor(color1, &c1); + shading->getColor(color2, &c2); for (i = 0; i < nComps; ++i) { - if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta || - abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) { + if (abs(c0.c[i] - c1.c[i]) > gouraudColorDelta || + abs(c1.c[i] - c2.c[i]) > gouraudColorDelta) { break; } } - if (i == nComps || depth == gouraudMaxDepth) { - state->setFillColor(color0); + state->transformDelta(x1 - x0, y1 - y0, &dx0, &dy0); + state->transformDelta(x2 - x1, y2 - y1, &dx1, &dy1); + state->transformDelta(x0 - x2, y0 - y2, &dx2, &dy2); + if (i == nComps || + depth == gouraudMaxDepth || + (fabs(dx0) < 0.5 && fabs(dy0) < 0.5 && + fabs(dx1) < 0.5 && fabs(dy1) < 0.5 && + fabs(dx2) < 0.5 && fabs(dy2) < 0.5)) { + state->setFillColor(&c0); out->updateFillColor(state); state->moveTo(x0, y0); state->lineTo(x1, y1); @@ -2907,21 +3070,19 @@ void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0, y12 = 0.5 * (y1 + y2); x20 = 0.5 * (x2 + x0); y20 = 0.5 * (y2 + y0); - //~ if the shading has a Function, this should interpolate on the - //~ function parameter, not on the color components - for (i = 0; i < nComps; ++i) { - color01.c[i] = (color0->c[i] + color1->c[i]) / 2; - color12.c[i] = (color1->c[i] + color2->c[i]) / 2; - color20.c[i] = (color2->c[i] + color0->c[i]) / 2; - } - gouraudFillTriangle(x0, y0, color0, x01, y01, &color01, - x20, y20, &color20, nComps, depth + 1); - gouraudFillTriangle(x01, y01, &color01, x1, y1, color1, - x12, y12, &color12, nComps, depth + 1); - gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12, - x20, y20, &color20, nComps, depth + 1); - gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12, - x2, y2, color2, nComps, depth + 1); + for (i = 0; i < shading->getNComps(); ++i) { + color01[i] = 0.5 * (color0[i] + color1[i]); + color12[i] = 0.5 * (color1[i] + color2[i]); + color20[i] = 0.5 * (color2[i] + color0[i]); + } + gouraudFillTriangle(x0, y0, color0, x01, y01, color01, + x20, y20, color20, shading, depth + 1); + gouraudFillTriangle(x01, y01, color01, x1, y1, color1, + x12, y12, color12, shading, depth + 1); + gouraudFillTriangle(x01, y01, color01, x12, y12, color12, + x20, y20, color20, shading, depth + 1); + gouraudFillTriangle(x20, y20, color20, x12, y12, color12, + x2, y2, color2, shading, depth + 1); } } @@ -2938,31 +3099,32 @@ void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) { start = 0; } for (i = 0; i < shading->getNPatches(); ++i) { - fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(), - start); + fillPatch(shading->getPatch(i), shading, start); } } -void Gfx::fillPatch(GfxPatch *patch, int nComps, int depth) { +void Gfx::fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth) { GfxPatch patch00, patch01, patch10, patch11; + GfxColor c00, c01, c10, c11; double xx[4][8], yy[4][8]; double xxm, yym; - int i; + int nComps, i; + nComps = shading->getColorSpace()->getNComps(); + shading->getColor(patch->color[0][0], &c00); + shading->getColor(patch->color[0][1], &c01); + shading->getColor(patch->color[1][0], &c10); + shading->getColor(patch->color[1][1], &c11); for (i = 0; i < nComps; ++i) { - if (abs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) - > patchColorDelta || - abs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) - > patchColorDelta || - abs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) - > patchColorDelta || - abs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) - > patchColorDelta) { + if (abs(c00.c[i] - c01.c[i]) > patchColorDelta || + abs(c01.c[i] - c11.c[i]) > patchColorDelta || + abs(c11.c[i] - c10.c[i]) > patchColorDelta || + abs(c10.c[i] - c00.c[i]) > patchColorDelta) { break; } } if (i == nComps || depth == patchMaxDepth) { - state->setFillColor(&patch->color[0][0]); + state->setFillColor(&c00); out->updateFillColor(state); state->moveTo(patch->x[0][0], patch->y[0][0]); state->curveTo(patch->x[0][1], patch->y[0][1], @@ -3039,35 +3201,33 @@ void Gfx::fillPatch(GfxPatch *patch, int nComps, int depth) { patch11.x[3][i-4] = xx[3][i]; patch11.y[3][i-4] = yy[3][i]; } - //~ if the shading has a Function, this should interpolate on the - //~ function parameter, not on the color components - for (i = 0; i < nComps; ++i) { - patch00.color[0][0].c[i] = patch->color[0][0].c[i]; - patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + - patch->color[0][1].c[i]) / 2; - patch01.color[0][0].c[i] = patch00.color[0][1].c[i]; - patch01.color[0][1].c[i] = patch->color[0][1].c[i]; - patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + - patch->color[1][1].c[i]) / 2; - patch11.color[0][1].c[i] = patch01.color[1][1].c[i]; - patch11.color[1][1].c[i] = patch->color[1][1].c[i]; - patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + - patch->color[1][0].c[i]) / 2; - patch10.color[1][1].c[i] = patch11.color[1][0].c[i]; - patch10.color[1][0].c[i] = patch->color[1][0].c[i]; - patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + - patch->color[0][0].c[i]) / 2; - patch00.color[1][0].c[i] = patch10.color[0][0].c[i]; - patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + - patch01.color[1][1].c[i]) / 2; - patch01.color[1][0].c[i] = patch00.color[1][1].c[i]; - patch11.color[0][0].c[i] = patch00.color[1][1].c[i]; - patch10.color[0][1].c[i] = patch00.color[1][1].c[i]; - } - fillPatch(&patch00, nComps, depth + 1); - fillPatch(&patch10, nComps, depth + 1); - fillPatch(&patch01, nComps, depth + 1); - fillPatch(&patch11, nComps, depth + 1); + for (i = 0; i < shading->getNComps(); ++i) { + patch00.color[0][0][i] = patch->color[0][0][i]; + patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] + + patch->color[0][1][i]); + patch01.color[0][0][i] = patch00.color[0][1][i]; + patch01.color[0][1][i] = patch->color[0][1][i]; + patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] + + patch->color[1][1][i]); + patch11.color[0][1][i] = patch01.color[1][1][i]; + patch11.color[1][1][i] = patch->color[1][1][i]; + patch11.color[1][0][i] = 0.5 * (patch->color[1][1][i] + + patch->color[1][0][i]); + patch10.color[1][1][i] = patch11.color[1][0][i]; + patch10.color[1][0][i] = patch->color[1][0][i]; + patch10.color[0][0][i] = 0.5 * (patch->color[1][0][i] + + patch->color[0][0][i]); + patch00.color[1][0][i] = patch10.color[0][0][i]; + patch00.color[1][1][i] = 0.5 * (patch00.color[1][0][i] + + patch01.color[1][1][i]); + patch01.color[1][0][i] = patch00.color[1][1][i]; + patch11.color[0][0][i] = patch00.color[1][1][i]; + patch10.color[0][1][i] = patch00.color[1][1][i]; + } + fillPatch(&patch00, shading, depth + 1); + fillPatch(&patch10, shading, depth + 1); + fillPatch(&patch01, shading, depth + 1); + fillPatch(&patch11, shading, depth + 1); } } @@ -3123,19 +3283,22 @@ void Gfx::opSetCharSpacing(Object args[], int numArgs) { } void Gfx::opSetFont(Object args[], int numArgs) { - GfxFont *font; + doSetFont(res->lookupFont(args[0].getName()), args[1].getNum()); +} - if (!(font = res->lookupFont(args[0].getName()))) { +void Gfx::doSetFont(GfxFont *font, double size) { + if (!font) { + state->setFont(NULL, 0); return; } if (printCommands) { printf(" font: tag=%s name='%s' %g\n", font->getTag()->getCString(), font->getName() ? font->getName()->getCString() : "???", - args[1].getNum()); + size); fflush(stdout); } - state->setFont(font, args[1].getNum()); + state->setFont(font, size); fontChanged = gTrue; } @@ -3343,7 +3506,7 @@ void Gfx::doShowText(GString *s) { double x0, y0, x1, y1; double oldCTM[6], newCTM[6]; double *mat; - Object charProc; + Object charProcRef, charProc; Dict *resDict; Parser *oldParser; GfxState *savedState; @@ -3427,12 +3590,13 @@ void Gfx::doShowText(GString *s) { state->transformDelta(dx, dy, &ddx, &ddy); if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, code, u, uLen)) { - ((Gfx8BitFont *)font)->getCharProc(code, &charProc); + ((Gfx8BitFont *)font)->getCharProcNF(code, &charProcRef); + charProcRef.fetch(xref, &charProc); if ((resDict = ((Gfx8BitFont *)font)->getResources())) { pushResources(resDict); } if (charProc.isStream()) { - display(&charProc, gFalse); + display(&charProcRef, gFalse); } else { error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry"); @@ -3442,6 +3606,7 @@ void Gfx::doShowText(GString *s) { popResources(); } charProc.free(); + charProcRef.free(); } restoreStateStack(savedState); curX += tdx; @@ -3592,44 +3757,53 @@ void Gfx::opXObject(Object args[], int numArgs) { obj1.free(); return; } +#if USE_EXCEPTIONS + try { +#endif #if OPI_SUPPORT - obj1.streamGetDict()->lookup("OPI", &opiDict); - if (opiDict.isDict()) { - out->opiBegin(state, opiDict.getDict()); - } + obj1.streamGetDict()->lookup("OPI", &opiDict); + if (opiDict.isDict()) { + out->opiBegin(state, opiDict.getDict()); + } #endif - obj1.streamGetDict()->lookup("Subtype", &obj2); - if (obj2.isName("Image")) { - if (out->needNonText()) { + obj1.streamGetDict()->lookup("Subtype", &obj2); + if (obj2.isName("Image")) { + if (out->needNonText()) { + res->lookupXObjectNF(name, &refObj); + doImage(&refObj, obj1.getStream(), gFalse); + refObj.free(); + } + } else if (obj2.isName("Form")) { res->lookupXObjectNF(name, &refObj); - doImage(&refObj, obj1.getStream(), gFalse); + if (out->useDrawForm() && refObj.isRef()) { + out->drawForm(refObj.getRef()); + } else { + doForm(&refObj, &obj1); + } refObj.free(); - } - } else if (obj2.isName("Form")) { - res->lookupXObjectNF(name, &refObj); - if (out->useDrawForm() && refObj.isRef()) { - out->drawForm(refObj.getRef()); + } else if (obj2.isName("PS")) { + obj1.streamGetDict()->lookup("Level1", &obj3); + out->psXObject(obj1.getStream(), + obj3.isStream() ? obj3.getStream() : (Stream *)NULL); + } else if (obj2.isName()) { + error(errSyntaxError, getPos(), + "Unknown XObject subtype '{0:s}'", obj2.getName()); } else { - doForm(&obj1); - } - refObj.free(); - } else if (obj2.isName("PS")) { - obj1.streamGetDict()->lookup("Level1", &obj3); - out->psXObject(obj1.getStream(), - obj3.isStream() ? obj3.getStream() : (Stream *)NULL); - } else if (obj2.isName()) { - error(errSyntaxError, getPos(), - "Unknown XObject subtype '{0:s}'", obj2.getName()); - } else { - error(errSyntaxError, getPos(), - "XObject subtype is missing or wrong type"); - } - obj2.free(); + error(errSyntaxError, getPos(), + "XObject subtype is missing or wrong type"); + } + obj2.free(); #if OPI_SUPPORT - if (opiDict.isDict()) { - out->opiEnd(state, opiDict.getDict()); + if (opiDict.isDict()) { + out->opiEnd(state, opiDict.getDict()); + } + opiDict.free(); +#endif +#if USE_EXCEPTIONS + } catch (GMemException e) { + obj1.free(); + throw; } - opiDict.free(); #endif obj1.free(); } @@ -3649,6 +3823,7 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { int maskWidth, maskHeight; GBool maskInvert; Stream *maskStr; + GBool interpolate; Object obj1, obj2; int i, n; @@ -3710,6 +3885,9 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { } if (obj1.isInt()) { bits = obj1.getInt(); + if (bits < 1 || bits > 16) { + goto err2; + } } else if (mask) { bits = 1; } else { @@ -3718,6 +3896,15 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { obj1.free(); } + // interpolate flag + dict->lookup("Interpolate", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("I", &obj1); + } + interpolate = obj1.isBool() && obj1.getBool(); + obj1.free(); + // display a mask if (mask) { @@ -3751,9 +3938,11 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { // draw it } else { if (state->getFillColorSpace()->getMode() == csPattern) { - doPatternImageMask(ref, str, width, height, invert, inlineImg); + doPatternImageMask(ref, str, width, height, invert, inlineImg, + interpolate); } else { - out->drawImageMask(state, ref, str, width, height, invert, inlineImg); + out->drawImageMask(state, ref, str, width, height, invert, inlineImg, + interpolate); } } @@ -3775,13 +3964,14 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { } } if (!obj1.isNull()) { - colorSpace = GfxColorSpace::parse(&obj1); + colorSpace = GfxColorSpace::parse(&obj1 + ); } else if (csMode == streamCSDeviceGray) { - colorSpace = new GfxDeviceGrayColorSpace(); + colorSpace = GfxColorSpace::create(csDeviceGray); } else if (csMode == streamCSDeviceRGB) { - colorSpace = new GfxDeviceRGBColorSpace(); + colorSpace = GfxColorSpace::create(csDeviceRGB); } else if (csMode == streamCSDeviceCMYK) { - colorSpace = new GfxDeviceCMYKColorSpace(); + colorSpace = GfxColorSpace::create(csDeviceCMYK); } else { colorSpace = NULL; } @@ -3860,7 +4050,8 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { obj2.free(); } } - maskColorSpace = GfxColorSpace::parse(&obj1); + maskColorSpace = GfxColorSpace::parse(&obj1 + ); obj1.free(); if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { goto err1; @@ -3977,14 +4168,17 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { } else { if (haveSoftMask) { out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskColorMap); + maskStr, maskWidth, maskHeight, maskColorMap, + interpolate); delete maskColorMap; } else if (haveExplicitMask) { out->drawMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskInvert); + maskStr, maskWidth, maskHeight, maskInvert, + interpolate); } else { out->drawImage(state, ref, str, width, height, colorMap, - haveColorKeyMask ? maskColors : (int *)NULL, inlineImg); + haveColorKeyMask ? maskColors : (int *)NULL, inlineImg, + interpolate); } } @@ -4006,7 +4200,7 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { error(errSyntaxError, getPos(), "Bad image parameters"); } -void Gfx::doForm(Object *str) { +void Gfx::doForm(Object *strRef, Object *str) { Dict *dict; GBool transpGroup, isolated, knockout; GfxColorSpace *blendingColorSpace; @@ -4087,7 +4281,8 @@ void Gfx::doForm(Object *str) { if (obj1.dictLookup("S", &obj2)->isName("Transparency")) { transpGroup = gTrue; if (!obj1.dictLookup("CS", &obj3)->isNull()) { - blendingColorSpace = GfxColorSpace::parse(&obj3); + blendingColorSpace = GfxColorSpace::parse(&obj3 + ); } obj3.free(); if (obj1.dictLookup("I", &obj3)->isBool()) { @@ -4105,7 +4300,7 @@ void Gfx::doForm(Object *str) { // draw it ++formDepth; - drawForm(str, resDict, m, bbox, + drawForm(strRef, resDict, m, bbox, transpGroup, gFalse, blendingColorSpace, isolated, knockout); --formDepth; @@ -4117,7 +4312,8 @@ void Gfx::doForm(Object *str) { ocState = ocSaved; } -void Gfx::drawForm(Object *str, Dict *resDict, double *matrix, double *bbox, +void Gfx::drawForm(Object *strRef, Dict *resDict, + double *matrix, double *bbox, GBool transpGroup, GBool softMask, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, @@ -4181,7 +4377,7 @@ void Gfx::drawForm(Object *str, Dict *resDict, double *matrix, double *bbox, } // draw the form - display(str, gFalse); + display(strRef, gFalse); if (softMask || transpGroup) { out->endTransparencyGroup(state); @@ -4210,13 +4406,17 @@ void Gfx::drawForm(Object *str, Dict *resDict, double *matrix, double *bbox, return; } +void Gfx::takeContentStreamStack(Gfx *oldGfx) { + contentStreamStack->append(oldGfx->contentStreamStack); +} + //------------------------------------------------------------------------ // in-line image operators //------------------------------------------------------------------------ void Gfx::opBeginImage(Object args[], int numArgs) { Stream *str; - int c1, c2; + int c1, c2, c3; // NB: this function is run even if ocState is false -- doImage() is // responsible for skipping over the inline image data @@ -4231,9 +4431,11 @@ void Gfx::opBeginImage(Object args[], int numArgs) { // skip 'EI' tag c1 = str->getUndecodedStream()->getChar(); c2 = str->getUndecodedStream()->getChar(); - while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) { + c3 = str->getUndecodedStream()->lookChar(); + while (!(c1 == 'E' && c2 == 'I' && Lexer::isSpace(c3)) && c3 != EOF) { c1 = c2; c2 = str->getUndecodedStream()->getChar(); + c3 = str->getUndecodedStream()->lookChar(); } delete str; } @@ -4328,9 +4530,7 @@ void Gfx::opBeginMarkedContent(Object args[], int numArgs) { GfxMarkedContent *mc; Object obj; GBool ocStateNew; - GString *s; - Unicode *u; - int uLen, i; + TextString *s; GfxMarkedContentKind mcKind; if (printCommands) { @@ -4351,24 +4551,9 @@ void Gfx::opBeginMarkedContent(Object args[], int numArgs) { mcKind = gfxMCOptionalContent; } else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) { if (args[1].dictLookup("ActualText", &obj)->isString()) { - s = obj.getString(); - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - uLen = (s->getLength() - 2) / 2; - u = (Unicode *)gmallocn(uLen, sizeof(Unicode)); - for (i = 0; i < uLen; ++i) { - u[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } - } else { - uLen = s->getLength(); - u = (Unicode *)gmallocn(uLen, sizeof(Unicode)); - for (i = 0; i < uLen; ++i) { - u[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } - } - out->beginActualText(state, u, uLen); - gfree(u); + s = new TextString(obj.getString()); + out->beginActualText(state, s->getUnicode(), s->getLength()); + delete s; mcKind = gfxMCActualText; } obj.free(); @@ -4416,14 +4601,14 @@ void Gfx::opMarkPoint(Object args[], int numArgs) { // misc //------------------------------------------------------------------------ -void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle, +void Gfx::drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, double xMin, double yMin, double xMax, double yMax) { Dict *dict, *resDict; - Object matrixObj, bboxObj, resObj, obj1; + Object str, matrixObj, bboxObj, resObj, obj1; double formXMin, formYMin, formXMax, formYMax; double x, y, sx, sy, tx, ty; double m[6], bbox[4]; - double r, g, b; + double *borderColor; GfxColor color; double *dash, *dash2; int dashLength; @@ -4439,16 +4624,18 @@ void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle, } // draw the appearance stream (if there is one) - if (str->isStream()) { + strRef->fetch(xref, &str); + if (str.isStream()) { // get stream dict - dict = str->streamGetDict(); + dict = str.streamGetDict(); // get the form bounding box dict->lookup("BBox", &bboxObj); if (!bboxObj.isArray()) { - bboxObj.free(); error(errSyntaxError, getPos(), "Bad form bounding box"); + bboxObj.free(); + str.free(); return; } for (i = 0; i < 4; ++i) { @@ -4548,22 +4735,43 @@ void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle, resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; // draw it - drawForm(str, resDict, m, bbox); + drawForm(strRef, resDict, m, bbox); resObj.free(); } + str.free(); // draw the border - if (borderStyle && borderStyle->getWidth() > 0) { - if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { - state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); - out->updateStrokeColorSpace(state); - } - borderStyle->getColor(&r, &g, &b); - color.c[0] = dblToCol(r); - color.c[1] = dblToCol(g); - color.c[2] = dblToCol(b); + if (borderStyle && borderStyle->getWidth() > 0 && + borderStyle->getNumColorComps() > 0) { + borderColor = borderStyle->getColor(); + switch (borderStyle->getNumColorComps()) { + case 1: + if (state->getStrokeColorSpace()->getMode() != csDeviceGray) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); + out->updateStrokeColorSpace(state); + } + break; + case 3: + if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB)); + out->updateStrokeColorSpace(state); + } + break; + case 4: + if (state->getStrokeColorSpace()->getMode() != csDeviceCMYK) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK)); + out->updateStrokeColorSpace(state); + } + break; + } + color.c[0] = dblToCol(borderColor[0]); + color.c[1] = dblToCol(borderColor[1]); + color.c[2] = dblToCol(borderColor[2]); + color.c[3] = dblToCol(borderColor[3]); state->setStrokeColor(&color); out->updateStrokeColor(state); state->setLineWidth(borderStyle->getWidth()); @@ -16,6 +16,7 @@ #endif #include "gtypes.h" +#include "gfile.h" class GString; class GList; @@ -84,13 +85,16 @@ public: ~GfxResources(); GfxFont *lookupFont(char *name); - GBool lookupXObject(char *name, Object *obj); - GBool lookupXObjectNF(char *name, Object *obj); - void lookupColorSpace(char *name, Object *obj); - GfxPattern *lookupPattern(char *name); - GfxShading *lookupShading(char *name); - GBool lookupGState(char *name, Object *obj); - GBool lookupPropertiesNF(char *name, Object *obj); + GfxFont *lookupFontByRef(Ref ref); + GBool lookupXObject(const char *name, Object *obj); + GBool lookupXObjectNF(const char *name, Object *obj); + void lookupColorSpace(const char *name, Object *obj); + GfxPattern *lookupPattern(const char *name + ); + GfxShading *lookupShading(const char *name + ); + GBool lookupGState(const char *name, Object *obj); + GBool lookupPropertiesNF(const char *name, Object *obj); GfxResources *getNext() { return next; } @@ -152,12 +156,13 @@ public: ~Gfx(); - // Interpret a stream or array of streams. - void display(Object *obj, GBool topLevel = gTrue); + // Interpret a stream or array of streams. <objRef> should be a + // reference wherever possible (for loop-checking). + void display(Object *objRef, GBool topLevel = gTrue); // Display an annotation, given its appearance (a Form XObject), // border style, and bounding box (in default user space). - void drawAnnot(Object *str, AnnotBorderStyle *borderStyle, + void drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, double xMin, double yMin, double xMax, double yMax); // Save graphics state. @@ -169,13 +174,21 @@ public: // Get the current graphics state object. GfxState *getState() { return state; } - void drawForm(Object *str, Dict *resDict, double *matrix, double *bbox, + void drawForm(Object *strRef, Dict *resDict, double *matrix, double *bbox, GBool transpGroup = gFalse, GBool softMask = gFalse, GfxColorSpace *blendingColorSpace = NULL, GBool isolated = gFalse, GBool knockout = gFalse, GBool alpha = gFalse, Function *transferFunc = NULL, GfxColor *backdropColor = NULL); + // Take all of the content stream stack entries from <oldGfx>. This + // is useful when creating a new Gfx object to handle a pattern, + // etc., where it's useful to check for loops that span both Gfx + // objects. This function should be called immediately after the + // Gfx constructor, i.e., before processing any content streams with + // the new Gfx object. + void takeContentStreamStack(Gfx *oldGfx); + private: PDFDoc *doc; @@ -201,6 +214,8 @@ private: GList *markedContentStack; // BMC/BDC/EMC stack [GfxMarkedContent] Parser *parser; // parser for page content stream(s) + GList *contentStreamStack; // stack of open content streams, used + // for loop-checking GBool // callback to check for an abort (*abortCheckCbk)(void *data); @@ -208,11 +223,12 @@ private: static Operator opTab[]; // table of operators + GBool checkForContentStreamLoop(Object *ref); void go(GBool topLevel); - void execOp(Object *cmd, Object args[], int numArgs); + GBool execOp(Object *cmd, Object args[], int numArgs); Operator *findOp(char *name); GBool checkArg(Object *arg, TchkType type); - int getPos(); + GFileOffset getPos(); // graphics state operators void opSave(Object args[], int numArgs); @@ -225,7 +241,7 @@ private: void opSetMiterLimit(Object args[], int numArgs); void opSetLineWidth(Object args[], int numArgs); void opSetExtGState(Object args[], int numArgs); - void doSoftMask(Object *str, GBool alpha, + void doSoftMask(Object *str, Object *strRef, GBool alpha, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, Function *transferFunc, GfxColor *backdropColor); @@ -268,7 +284,7 @@ private: void doPatternStroke(); void doPatternText(); void doPatternImageMask(Object *ref, Stream *str, int width, int height, - GBool invert, GBool inlineImg); + GBool invert, GBool inlineImg, GBool interpolate); void doTilingPatternFill(GfxTilingPattern *tPat, GBool stroke, GBool eoFill, GBool text); void doShadingPatternFill(GfxShadingPattern *sPat, @@ -282,12 +298,12 @@ private: void doAxialShFill(GfxAxialShading *shading); void doRadialShFill(GfxRadialShading *shading); void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading); - void gouraudFillTriangle(double x0, double y0, GfxColor *color0, - double x1, double y1, GfxColor *color1, - double x2, double y2, GfxColor *color2, - int nComps, int depth); + void gouraudFillTriangle(double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading, int depth); void doPatchMeshShFill(GfxPatchMeshShading *shading); - void fillPatch(GfxPatch *patch, int nComps, int depth); + void fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth); void doEndPath(); // path clipping operators @@ -301,6 +317,7 @@ private: // text state operators void opSetCharSpacing(Object args[], int numArgs); void opSetFont(Object args[], int numArgs); + void doSetFont(GfxFont *font, double size); void opSetTextLeading(Object args[], int numArgs); void opSetTextRender(Object args[], int numArgs); void opSetTextRise(Object args[], int numArgs); @@ -324,7 +341,7 @@ private: // XObject operators void opXObject(Object args[], int numArgs); void doImage(Object *ref, Stream *str, GBool inlineImg); - void doForm(Object *str); + void doForm(Object *strRef, Object *str); // in-line image operators void opBeginImage(Object args[], int numArgs); diff --git a/xpdf/GfxFont.cc b/xpdf/GfxFont.cc index aa88e78..0ebb5b9 100644 --- a/xpdf/GfxFont.cc +++ b/xpdf/GfxFont.cc @@ -146,6 +146,7 @@ static int readFromStream(void *data) { GfxFontLoc::GfxFontLoc() { path = NULL; fontNum = 0; + oblique = 0; encoding = NULL; substIdx = -1; } @@ -175,6 +176,8 @@ GfxFont *GfxFont::makeFont(XRef *xref, char *tagA, Ref idA, Dict *fontDict) { fontDict->lookup("BaseFont", &obj1); if (obj1.isName()) { nameA = new GString(obj1.getName()); + } else if (obj1.isString()) { + nameA = obj1.getString()->copy(); } obj1.free(); @@ -451,7 +454,7 @@ void GfxFont::readFontDescriptor(XRef *xref, Dict *fontDict) { } // some broken font descriptors set ascent and descent to 0; // others set it to ridiculous values (e.g., 32768) - if (t != 0 && t < 3) { + if (t != 0 && t < 1.9) { ascent = t; } } @@ -464,7 +467,7 @@ void GfxFont::readFontDescriptor(XRef *xref, Dict *fontDict) { t = -t; } // some broken font descriptors set ascent and descent to 0 - if (t != 0 && t > -3) { + if (t != 0 && t > -1.9) { descent = t; } } @@ -489,7 +492,8 @@ CharCodeToUnicode *GfxFont::readToUnicodeCMap(Dict *fontDict, int nBits, CharCodeToUnicode *ctu) { GString *buf; Object obj1; - int c; + char buf2[4096]; + int n; if (!fontDict->lookup("ToUnicode", &obj1)->isStream()) { obj1.free(); @@ -497,8 +501,8 @@ CharCodeToUnicode *GfxFont::readToUnicodeCMap(Dict *fontDict, int nBits, } buf = new GString(); obj1.streamReset(); - while ((c = obj1.streamGetChar()) != EOF) { - buf->append(c); + while ((n = obj1.streamGetBlock(buf2, sizeof(buf2))) > 0) { + buf->append(buf2, n); } obj1.streamClose(); obj1.free(); @@ -518,6 +522,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { PSFontParam16 *psFont16; Object refObj, embFontObj; int substIdx, fontNum; + double oblique; GBool embed; if (type == fontType3) { @@ -570,7 +575,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { } //----- PS passthrough - if (ps && !isCIDFont() && globalParams->getPSFontPassthrough()) { + if (ps && name && !isCIDFont() && globalParams->getPSFontPassthrough()) { fontLoc = new GfxFontLoc(); fontLoc->locType = gfxFontLocResident; fontLoc->fontType = fontType1; @@ -578,6 +583,13 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { return fontLoc; } + //----- external font file (fontFile, fontDir) + if (name && (path = globalParams->findFontFile(name))) { + if ((fontLoc = getExternalFont(path, 0, 0, isCIDFont()))) { + return fontLoc; + } + } + //----- PS resident Base-14 font if (ps && !isCIDFont() && ((Gfx8BitFont *)this)->base14) { fontLoc = new GfxFontLoc(); @@ -587,28 +599,19 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { return fontLoc; } - //----- external font file (fontFile, fontDir) - if ((path = globalParams->findFontFile(name))) { - if ((fontLoc = getExternalFont(path, isCIDFont()))) { - return fontLoc; - } - } - //----- external font file for Base-14 font if (!ps && !isCIDFont() && ((Gfx8BitFont *)this)->base14) { base14Name = new GString(((Gfx8BitFont *)this)->base14->base14Name); - if ((path = globalParams->findFontFile(base14Name))) { - if ((fontLoc = getExternalFont(path, gFalse))) { - delete base14Name; - return fontLoc; - } - } + path = globalParams->findBase14FontFile(base14Name, &fontNum, &oblique); delete base14Name; + if (path && (fontLoc = getExternalFont(path, fontNum, oblique, gFalse))) { + return fontLoc; + } } //----- system font - if ((path = globalParams->findSystemFontFile(name, &sysFontType, - &fontNum))) { + if (name && (path = globalParams->findSystemFontFile(name, &sysFontType, + &fontNum))) { if (isCIDFont()) { if (sysFontType == sysFontTTF || sysFontType == sysFontTTC) { fontLoc = new GfxFontLoc(); @@ -624,13 +627,13 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { fontLoc->locType = gfxFontLocExternal; fontLoc->fontType = fontTrueType; fontLoc->path = path; + fontLoc->fontNum = fontNum; return fontLoc; } else if (sysFontType == sysFontPFA || sysFontType == sysFontPFB) { fontLoc = new GfxFontLoc(); fontLoc->locType = gfxFontLocExternal; fontLoc->fontType = fontType1; fontLoc->path = path; - fontLoc->fontNum = fontNum; return fontLoc; } } @@ -641,7 +644,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { //----- 8-bit PS resident font if (ps) { - if ((path = globalParams->getPSResidentFont(name))) { + if (name && (path = globalParams->getPSResidentFont(name))) { fontLoc = new GfxFontLoc(); fontLoc->locType = gfxFontLocResident; fontLoc->fontType = fontType1; @@ -675,10 +678,10 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { fontLoc->substIdx = substIdx; return fontLoc; } else { - path = globalParams->findFontFile(substName); + path = globalParams->findBase14FontFile(substName, &fontNum, &oblique); delete substName; if (path) { - if ((fontLoc = getExternalFont(path, gFalse))) { + if ((fontLoc = getExternalFont(path, fontNum, oblique, gFalse))) { error(errSyntaxWarning, -1, "Substituting font '{0:s}' for '{1:t}'", base14SubstFonts[substIdx], name); fontLoc->substIdx = substIdx; @@ -692,7 +695,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { } //----- 16-bit PS resident font - if (ps && ((psFont16 = globalParams->getPSResidentFont16( + if (ps && name && ((psFont16 = globalParams->getPSResidentFont16( name, ((GfxCIDFont *)this)->getWMode())))) { fontLoc = new GfxFontLoc(); @@ -720,7 +723,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { //----- CID font substitution if ((path = globalParams->findCCFontFile( ((GfxCIDFont *)this)->getCollection()))) { - if ((fontLoc = getExternalFont(path, gTrue))) { + if ((fontLoc = getExternalFont(path, 0, 0, gTrue))) { error(errSyntaxWarning, -1, "Substituting font '{0:t}' for '{1:t}'", fontLoc->path, name); return fontLoc; @@ -733,15 +736,18 @@ GfxFontLoc *GfxFont::locateFont(XRef *xref, GBool ps) { GfxFontLoc *GfxFont::locateBase14Font(GString *base14Name) { GString *path; + int fontNum; + double oblique; - path = globalParams->findFontFile(base14Name); + path = globalParams->findBase14FontFile(base14Name, &fontNum, &oblique); if (!path) { return NULL; } - return getExternalFont(path, gFalse); + return getExternalFont(path, fontNum, oblique, gFalse); } -GfxFontLoc *GfxFont::getExternalFont(GString *path, GBool cid) { +GfxFontLoc *GfxFont::getExternalFont(GString *path, int fontNum, + double oblique, GBool cid) { FoFiIdentifierType fft; GfxFontType fontType; GfxFontLoc *fontLoc; @@ -768,6 +774,9 @@ GfxFontLoc *GfxFont::getExternalFont(GString *path, GBool cid) { case fofiIdOpenTypeCFFCID: fontType = fontCIDType0COT; break; + case fofiIdDfont: + fontType = cid ? fontCIDType2 : fontTrueType; + break; case fofiIdUnknown: case fofiIdError: default: @@ -784,6 +793,8 @@ GfxFontLoc *GfxFont::getExternalFont(GString *path, GBool cid) { fontLoc->locType = gfxFontLocExternal; fontLoc->fontType = fontType; fontLoc->path = path; + fontLoc->fontNum = fontNum; + fontLoc->oblique = oblique; return fontLoc; } @@ -791,8 +802,7 @@ char *GfxFont::readEmbFontFile(XRef *xref, int *len) { char *buf; Object obj1, obj2; Stream *str; - int c; - int size, i; + int size, n; obj1.initRef(embFontID.num, embFontID.gen); obj1.fetch(xref, &obj2); @@ -805,21 +815,19 @@ char *GfxFont::readEmbFontFile(XRef *xref, int *len) { } str = obj2.getStream(); + size = 0; buf = NULL; - i = size = 0; str->reset(); - while ((c = str->getChar()) != EOF) { - if (i == size) { - if (size > INT_MAX - 4096) { - error(errSyntaxError, -1, "Embedded font file is too large"); - break; - } - size += 4096; - buf = (char *)grealloc(buf, size); - } - buf[i++] = c; - } - *len = i; + do { + if (size > INT_MAX - 4096) { + error(errSyntaxError, -1, "Embedded font file is too large"); + break; + } + buf = (char *)grealloc(buf, size + 4096); + n = str->getBlock(buf + size, 4096); + size += n; + } while (n == 4096); + *len = size; str->close(); obj2.free(); @@ -908,8 +916,8 @@ Gfx8BitFont::Gfx8BitFont(XRef *xref, char *tagA, Ref idA, GString *nameA, fontBBox[2] = 0.001 * builtinFont->bbox[2]; fontBBox[3] = 0.001 * builtinFont->bbox[3]; } else { - ascent = 0.95; - descent = -0.35; + ascent = 0.75; + descent = -0.25; fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0; } @@ -1491,6 +1499,15 @@ Object *Gfx8BitFont::getCharProc(int code, Object *proc) { return proc; } +Object *Gfx8BitFont::getCharProcNF(int code, Object *proc) { + if (enc[code] && charProcs.isDict()) { + charProcs.dictLookupNF(enc[code], proc); + } else { + proc->initNull(); + } + return proc; +} + Dict *Gfx8BitFont::getResources() { return resources.isDict() ? resources.getDict() : (Dict *)NULL; } @@ -1565,7 +1582,6 @@ GfxCIDFont::GfxCIDFont(XRef *xref, char *tagA, Ref idA, GString *nameA, error(errSyntaxError, -1, "Missing or empty DescendantFonts entry in Type 0 font"); obj1.free(); - goto err1; } if (!obj1.arrayGet(0, &desFontDictObj)->isDict()) { @@ -1661,6 +1677,7 @@ GfxCIDFont::GfxCIDFont(XRef *xref, char *tagA, Ref idA, GString *nameA, } cidToGID[cidToGIDLen++] = (c1 << 8) + c2; } + obj1.streamClose(); } else if (!obj1.isName("Identity") && !obj1.isNull()) { error(errSyntaxError, -1, "Invalid CIDToGIDMap entry in CID font"); } @@ -1836,7 +1853,8 @@ GfxCIDFont::GfxCIDFont(XRef *xref, char *tagA, Ref idA, GString *nameA, err2: obj1.free(); desFontDictObj.free(); - err1:; + err1: + error(errSyntaxError, -1, "Failed to parse font object for '{0:t}'", name); } GfxCIDFont::~GfxCIDFont() { @@ -2016,3 +2034,16 @@ GfxFont *GfxFontDict::lookup(char *tag) { } return NULL; } + +GfxFont *GfxFontDict::lookupByRef(Ref ref) { + int i; + + for (i = 0; i < numFonts; ++i) { + if (fonts[i] && + fonts[i]->getID()->num == ref.num && + fonts[i]->getID()->gen == ref.gen) { + return fonts[i]; + } + } + return NULL; +} diff --git a/xpdf/GfxFont.h b/xpdf/GfxFont.h index db45ef0..1ec46ec 100644 --- a/xpdf/GfxFont.h +++ b/xpdf/GfxFont.h @@ -100,8 +100,11 @@ public: // (if locType == gfxFontLocExternal) // PS font name // (if locType == gfxFontLocResident) - int fontNum; // for TrueType collections + int fontNum; // for TrueType collections and Mac dfonts // (if locType == gfxFontLocExternal) + double oblique; // sheer factor to oblique this font + // (used when substituting a plain + // font for an oblique font) GString *encoding; // PS font encoding, only for 16-bit fonts // (if locType == gfxFontLocResident) int wMode; // writing mode, only for 16-bit fonts @@ -207,7 +210,8 @@ protected: void readFontDescriptor(XRef *xref, Dict *fontDict); CharCodeToUnicode *readToUnicodeCMap(Dict *fontDict, int nBits, CharCodeToUnicode *ctu); - static GfxFontLoc *getExternalFont(GString *path, GBool cid); + static GfxFontLoc *getExternalFont(GString *path, int fontNum, + double oblique, GBool cid); GString *tag; // PDF font tag Ref id; // reference (used as unique ID) @@ -267,6 +271,7 @@ public: // Return the Type 3 CharProc for the character associated with <code>. Object *getCharProc(int code, Object *proc); + Object *getCharProcNF(int code, Object *proc); // Return the Type 3 Resources dictionary, or NULL if none. Dict *getResources(); @@ -347,6 +352,7 @@ public: // Get the specified font. GfxFont *lookup(char *tag); + GfxFont *lookupByRef(Ref ref); // Iterative access. int getNumFonts() { return numFonts; } diff --git a/xpdf/GfxState.cc b/xpdf/GfxState.cc index cf5e7c2..bdaa5e6 100644 --- a/xpdf/GfxState.cc +++ b/xpdf/GfxState.cc @@ -20,6 +20,7 @@ #include "Object.h" #include "Array.h" #include "Page.h" +#include "XRef.h" #include "GfxState.h" //------------------------------------------------------------------------ @@ -28,7 +29,6 @@ // loops in the color space object structure. #define colorSpaceRecursionLimit 8 - //------------------------------------------------------------------------ static inline GfxColorComp clip01(GfxColorComp x) { @@ -101,7 +101,8 @@ GfxColorSpace::GfxColorSpace() { GfxColorSpace::~GfxColorSpace() { } -GfxColorSpace *GfxColorSpace::parse(Object *csObj, int recursion) { +GfxColorSpace *GfxColorSpace::parse(Object *csObj, + int recursion) { GfxColorSpace *cs; Object obj1; @@ -112,11 +113,11 @@ GfxColorSpace *GfxColorSpace::parse(Object *csObj, int recursion) { cs = NULL; if (csObj->isName()) { if (csObj->isName("DeviceGray") || csObj->isName("G")) { - cs = new GfxDeviceGrayColorSpace(); + cs = GfxColorSpace::create(csDeviceGray); } else if (csObj->isName("DeviceRGB") || csObj->isName("RGB")) { - cs = new GfxDeviceRGBColorSpace(); + cs = GfxColorSpace::create(csDeviceRGB); } else if (csObj->isName("DeviceCMYK") || csObj->isName("CMYK")) { - cs = new GfxDeviceCMYKColorSpace(); + cs = GfxColorSpace::create(csDeviceCMYK); } else if (csObj->isName("Pattern")) { cs = new GfxPatternColorSpace(NULL); } else { @@ -125,11 +126,11 @@ GfxColorSpace *GfxColorSpace::parse(Object *csObj, int recursion) { } else if (csObj->isArray() && csObj->arrayGetLength() > 0) { csObj->arrayGet(0, &obj1); if (obj1.isName("DeviceGray") || obj1.isName("G")) { - cs = new GfxDeviceGrayColorSpace(); + cs = GfxColorSpace::create(csDeviceGray); } else if (obj1.isName("DeviceRGB") || obj1.isName("RGB")) { - cs = new GfxDeviceRGBColorSpace(); + cs = GfxColorSpace::create(csDeviceRGB); } else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) { - cs = new GfxDeviceCMYKColorSpace(); + cs = GfxColorSpace::create(csDeviceCMYK); } else if (obj1.isName("CalGray")) { cs = GfxCalGrayColorSpace::parse(csObj->getArray(), recursion); } else if (obj1.isName("CalRGB")) { @@ -137,15 +138,20 @@ GfxColorSpace *GfxColorSpace::parse(Object *csObj, int recursion) { } else if (obj1.isName("Lab")) { cs = GfxLabColorSpace::parse(csObj->getArray(), recursion); } else if (obj1.isName("ICCBased")) { - cs = GfxICCBasedColorSpace::parse(csObj->getArray(), recursion); + cs = GfxICCBasedColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("Indexed") || obj1.isName("I")) { - cs = GfxIndexedColorSpace::parse(csObj->getArray(), recursion); + cs = GfxIndexedColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("Separation")) { - cs = GfxSeparationColorSpace::parse(csObj->getArray(), recursion); + cs = GfxSeparationColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("DeviceN")) { - cs = GfxDeviceNColorSpace::parse(csObj->getArray(), recursion); + cs = GfxDeviceNColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("Pattern")) { - cs = GfxPatternColorSpace::parse(csObj->getArray(), recursion); + cs = GfxPatternColorSpace::parse(csObj->getArray(), + recursion); } else { error(errSyntaxError, -1, "Bad color space"); } @@ -156,6 +162,20 @@ GfxColorSpace *GfxColorSpace::parse(Object *csObj, int recursion) { return cs; } +GfxColorSpace *GfxColorSpace::create(GfxColorSpaceMode mode) { + GfxColorSpace *cs; + + cs = NULL; + if (mode == csDeviceGray) { + cs = new GfxDeviceGrayColorSpace(); + } else if (mode == csDeviceRGB) { + cs = new GfxDeviceRGBColorSpace(); + } else if (mode == csDeviceCMYK) { + cs = new GfxDeviceCMYKColorSpace(); + } + return cs; +} + void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) { int i; @@ -185,9 +205,13 @@ GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() { } GfxColorSpace *GfxDeviceGrayColorSpace::copy() { - return new GfxDeviceGrayColorSpace(); + GfxDeviceGrayColorSpace *cs; + + cs = new GfxDeviceGrayColorSpace(); + return cs; } + void GfxDeviceGrayColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01(color->c[0]); } @@ -233,6 +257,7 @@ GfxColorSpace *GfxCalGrayColorSpace::copy() { return cs; } + GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, int recursion) { GfxCalGrayColorSpace *cs; Object obj1, obj2, obj3; @@ -311,9 +336,13 @@ GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() { } GfxColorSpace *GfxDeviceRGBColorSpace::copy() { - return new GfxDeviceRGBColorSpace(); + GfxDeviceRGBColorSpace *cs; + + cs = new GfxDeviceRGBColorSpace(); + return cs; } + void GfxDeviceRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01((GfxColorComp)(0.3 * color->c[0] + 0.59 * color->c[1] + @@ -456,6 +485,7 @@ GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, int recursion) { return cs; } + void GfxCalRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01((GfxColorComp)(0.299 * color->c[0] + 0.587 * color->c[1] + @@ -505,9 +535,13 @@ GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() { } GfxColorSpace *GfxDeviceCMYKColorSpace::copy() { - return new GfxDeviceCMYKColorSpace(); + GfxDeviceCMYKColorSpace *cs; + + cs = new GfxDeviceCMYKColorSpace(); + return cs; } + void GfxDeviceCMYKColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01((GfxColorComp)(gfxColorComp1 - color->c[3] - 0.3 * color->c[0] @@ -706,6 +740,7 @@ GfxColorSpace *GfxLabColorSpace::parse(Array *arr, int recursion) { return cs; } + void GfxLabColorSpace::getGray(GfxColor *color, GfxGray *gray) { GfxRGB rgb; @@ -720,6 +755,7 @@ void GfxLabColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) { double t1, t2; double r, g, b; + // convert L*a*b* to CIE 1931 XYZ color space t1 = (colToDbl(color->c[0]) + 16) / 116; t2 = t1 + colToDbl(color->c[1]) / 500; @@ -756,6 +792,7 @@ void GfxLabColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) { GfxRGB rgb; GfxColorComp c, m, y, k; + getRGB(color, &rgb); c = clip01(gfxColorComp1 - rgb.r); m = clip01(gfxColorComp1 - rgb.g); @@ -831,7 +868,8 @@ GfxColorSpace *GfxICCBasedColorSpace::copy() { return cs; } -GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, + int recursion) { GfxICCBasedColorSpace *cs; Ref iccProfileStreamA; int nCompsA; @@ -874,16 +912,17 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, int recursion) { nCompsA = 4; } if (dict->lookup("Alternate", &obj2)->isNull() || - !(altA = GfxColorSpace::parse(&obj2, recursion + 1))) { + !(altA = GfxColorSpace::parse(&obj2, + recursion + 1))) { switch (nCompsA) { case 1: - altA = new GfxDeviceGrayColorSpace(); + altA = GfxColorSpace::create(csDeviceGray); break; case 3: - altA = new GfxDeviceRGBColorSpace(); + altA = GfxColorSpace::create(csDeviceRGB); break; case 4: - altA = new GfxDeviceCMYKColorSpace(); + altA = GfxColorSpace::create(csDeviceCMYK); break; default: error(errSyntaxError, -1, "Bad ICCBased color space - invalid N"); @@ -910,6 +949,7 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, int recursion) { return cs; } + void GfxICCBasedColorSpace::getGray(GfxColor *color, GfxGray *gray) { alt->getGray(color, gray); } @@ -981,7 +1021,8 @@ GfxColorSpace *GfxIndexedColorSpace::copy() { return cs; } -GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, + int recursion) { GfxIndexedColorSpace *cs; GfxColorSpace *baseA; int indexHighA; @@ -995,7 +1036,8 @@ GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, int recursion) { goto err1; } arr->get(1, &obj1); - if (!(baseA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(baseA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad Indexed color space (base color space)"); goto err2; } @@ -1060,6 +1102,7 @@ GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, int recursion) { return NULL; } + GfxColor *GfxIndexedColorSpace::mapColorToBase(GfxColor *color, GfxColor *baseColor) { Guchar *p; @@ -1146,12 +1189,16 @@ GfxSeparationColorSpace::~GfxSeparationColorSpace() { } GfxColorSpace *GfxSeparationColorSpace::copy() { - return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), - nonMarking, overprintMask); + GfxSeparationColorSpace *cs; + + cs = new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), + nonMarking, overprintMask); + return cs; } //~ handle the 'All' and 'None' colorants -GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, + int recursion) { GfxSeparationColorSpace *cs; GString *nameA; GfxColorSpace *altA; @@ -1169,7 +1216,8 @@ GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, int recursion) { nameA = new GString(obj1.getName()); obj1.free(); arr->get(2, &obj1); - if (!(altA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(altA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad Separation color space (alternate color space)"); goto err3; @@ -1193,6 +1241,7 @@ GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, int recursion) { return NULL; } + void GfxSeparationColorSpace::getGray(GfxColor *color, GfxGray *gray) { double x; double c[gfxColorMaxComps]; @@ -1303,12 +1352,16 @@ GfxDeviceNColorSpace::~GfxDeviceNColorSpace() { } GfxColorSpace *GfxDeviceNColorSpace::copy() { - return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), - nonMarking, overprintMask); + GfxDeviceNColorSpace *cs; + + cs = new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), + nonMarking, overprintMask); + return cs; } //~ handle the 'None' colorant -GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, + int recursion) { GfxDeviceNColorSpace *cs; int nCompsA; GString *namesA[gfxColorMaxComps]; @@ -1343,7 +1396,8 @@ GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, int recursion) { } obj1.free(); arr->get(2, &obj1); - if (!(altA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(altA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad DeviceN color space (alternate color space)"); goto err3; @@ -1369,6 +1423,7 @@ GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, int recursion) { return NULL; } + void GfxDeviceNColorSpace::getGray(GfxColor *color, GfxGray *gray) { double x[gfxColorMaxComps], c[gfxColorMaxComps]; GfxColor color2; @@ -1438,11 +1493,15 @@ GfxPatternColorSpace::~GfxPatternColorSpace() { } GfxColorSpace *GfxPatternColorSpace::copy() { - return new GfxPatternColorSpace(under ? under->copy() : - (GfxColorSpace *)NULL); + GfxPatternColorSpace *cs; + + cs = new GfxPatternColorSpace(under ? under->copy() : + (GfxColorSpace *)NULL); + return cs; } -GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, + int recursion) { GfxPatternColorSpace *cs; GfxColorSpace *underA; Object obj1; @@ -1454,7 +1513,8 @@ GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, int recursion) { underA = NULL; if (arr->getLength() == 2) { arr->get(1, &obj1); - if (!(underA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(underA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad Pattern color space (underlying color space)"); obj1.free(); @@ -1466,6 +1526,7 @@ GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, int recursion) { return cs; } + void GfxPatternColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = 0; } @@ -1495,24 +1556,26 @@ GfxPattern::GfxPattern(int typeA) { GfxPattern::~GfxPattern() { } -GfxPattern *GfxPattern::parse(Object *obj) { +GfxPattern *GfxPattern::parse(Object *objRef, Object *obj + ) { GfxPattern *pattern; - Object obj1; + Object typeObj; if (obj->isDict()) { - obj->dictLookup("PatternType", &obj1); + obj->dictLookup("PatternType", &typeObj); } else if (obj->isStream()) { - obj->streamGetDict()->lookup("PatternType", &obj1); + obj->streamGetDict()->lookup("PatternType", &typeObj); } else { return NULL; } pattern = NULL; - if (obj1.isInt() && obj1.getInt() == 1) { - pattern = GfxTilingPattern::parse(obj); - } else if (obj1.isInt() && obj1.getInt() == 2) { - pattern = GfxShadingPattern::parse(obj); + if (typeObj.isInt() && typeObj.getInt() == 1) { + pattern = GfxTilingPattern::parse(objRef, obj); + } else if (typeObj.isInt() && typeObj.getInt() == 2) { + pattern = GfxShadingPattern::parse(obj + ); } - obj1.free(); + typeObj.free(); return pattern; } @@ -1520,7 +1583,7 @@ GfxPattern *GfxPattern::parse(Object *obj) { // GfxTilingPattern //------------------------------------------------------------------------ -GfxTilingPattern *GfxTilingPattern::parse(Object *patObj) { +GfxTilingPattern *GfxTilingPattern::parse(Object *patObjRef, Object *patObj) { GfxTilingPattern *pat; Dict *dict; int paintTypeA, tilingTypeA; @@ -1597,7 +1660,7 @@ GfxTilingPattern *GfxTilingPattern::parse(Object *patObj) { obj1.free(); pat = new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA, - &resDictA, matrixA, patObj); + &resDictA, matrixA, patObjRef); resDictA.free(); return pat; } @@ -1605,7 +1668,7 @@ GfxTilingPattern *GfxTilingPattern::parse(Object *patObj) { GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA, double *bboxA, double xStepA, double yStepA, Object *resDictA, double *matrixA, - Object *contentStreamA): + Object *contentStreamRefA): GfxPattern(1) { int i; @@ -1621,24 +1684,25 @@ GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA, for (i = 0; i < 6; ++i) { matrix[i] = matrixA[i]; } - contentStreamA->copy(&contentStream); + contentStreamRefA->copy(&contentStreamRef); } GfxTilingPattern::~GfxTilingPattern() { resDict.free(); - contentStream.free(); + contentStreamRef.free(); } GfxPattern *GfxTilingPattern::copy() { return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep, - &resDict, matrix, &contentStream); + &resDict, matrix, &contentStreamRef); } //------------------------------------------------------------------------ // GfxShadingPattern //------------------------------------------------------------------------ -GfxShadingPattern *GfxShadingPattern::parse(Object *patObj) { +GfxShadingPattern *GfxShadingPattern::parse(Object *patObj + ) { Dict *dict; GfxShading *shadingA; double matrixA[6]; @@ -1651,7 +1715,8 @@ GfxShadingPattern *GfxShadingPattern::parse(Object *patObj) { dict = patObj->getDict(); dict->lookup("Shading", &obj1); - shadingA = GfxShading::parse(&obj1); + shadingA = GfxShading::parse(&obj1 + ); obj1.free(); if (!shadingA) { return NULL; @@ -1724,7 +1789,8 @@ GfxShading::~GfxShading() { } } -GfxShading *GfxShading::parse(Object *obj) { +GfxShading *GfxShading::parse(Object *obj + ) { GfxShading *shading; Dict *dict; int typeA; @@ -1748,17 +1814,21 @@ GfxShading *GfxShading::parse(Object *obj) { switch (typeA) { case 1: - shading = GfxFunctionShading::parse(dict); + shading = GfxFunctionShading::parse(dict + ); break; case 2: - shading = GfxAxialShading::parse(dict); + shading = GfxAxialShading::parse(dict + ); break; case 3: - shading = GfxRadialShading::parse(dict); + shading = GfxRadialShading::parse(dict + ); break; case 4: if (obj->isStream()) { - shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream()); + shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 4 shading object"); goto err1; @@ -1766,7 +1836,8 @@ GfxShading *GfxShading::parse(Object *obj) { break; case 5: if (obj->isStream()) { - shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream()); + shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 5 shading object"); goto err1; @@ -1774,7 +1845,8 @@ GfxShading *GfxShading::parse(Object *obj) { break; case 6: if (obj->isStream()) { - shading = GfxPatchMeshShading::parse(6, dict, obj->getStream()); + shading = GfxPatchMeshShading::parse(6, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 6 shading object"); goto err1; @@ -1782,7 +1854,8 @@ GfxShading *GfxShading::parse(Object *obj) { break; case 7: if (obj->isStream()) { - shading = GfxPatchMeshShading::parse(7, dict, obj->getStream()); + shading = GfxPatchMeshShading::parse(7, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 7 shading object"); goto err1; @@ -1799,12 +1872,14 @@ GfxShading *GfxShading::parse(Object *obj) { return NULL; } -GBool GfxShading::init(Dict *dict) { +GBool GfxShading::init(Dict *dict + ) { Object obj1, obj2; int i; dict->lookup("ColorSpace", &obj1); - if (!(colorSpace = GfxColorSpace::parse(&obj1))) { + if (!(colorSpace = GfxColorSpace::parse(&obj1 + ))) { error(errSyntaxError, -1, "Bad color space in shading dictionary"); obj1.free(); return gFalse; @@ -1901,7 +1976,8 @@ GfxFunctionShading::~GfxFunctionShading() { } } -GfxFunctionShading *GfxFunctionShading::parse(Dict *dict) { +GfxFunctionShading *GfxFunctionShading::parse(Dict *dict + ) { GfxFunctionShading *shading; double x0A, y0A, x1A, y1A; double matrixA[6]; @@ -1916,9 +1992,9 @@ GfxFunctionShading *GfxFunctionShading::parse(Dict *dict) { obj1.arrayGetLength() == 4) { x0A = obj1.arrayGet(0, &obj2)->getNum(); obj2.free(); - y0A = obj1.arrayGet(1, &obj2)->getNum(); + x1A = obj1.arrayGet(1, &obj2)->getNum(); obj2.free(); - x1A = obj1.arrayGet(2, &obj2)->getNum(); + y0A = obj1.arrayGet(2, &obj2)->getNum(); obj2.free(); y1A = obj1.arrayGet(3, &obj2)->getNum(); obj2.free(); @@ -1970,7 +2046,8 @@ GfxFunctionShading *GfxFunctionShading::parse(Dict *dict) { shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA, funcsA, nFuncsA); - if (!shading->init(dict)) { + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2060,7 +2137,8 @@ GfxAxialShading::~GfxAxialShading() { } } -GfxAxialShading *GfxAxialShading::parse(Dict *dict) { +GfxAxialShading *GfxAxialShading::parse(Dict *dict + ) { GfxAxialShading *shading; double x0A, y0A, x1A, y1A; double t0A, t1A; @@ -2137,7 +2215,8 @@ GfxAxialShading *GfxAxialShading::parse(Dict *dict) { shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A, funcsA, nFuncsA, extend0A, extend1A); - if (!shading->init(dict)) { + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2226,7 +2305,8 @@ GfxRadialShading::~GfxRadialShading() { } } -GfxRadialShading *GfxRadialShading::parse(Dict *dict) { +GfxRadialShading *GfxRadialShading::parse(Dict *dict + ) { GfxRadialShading *shading; double x0A, y0A, r0A, x1A, y1A, r1A; double t0A, t1A; @@ -2307,7 +2387,8 @@ GfxRadialShading *GfxRadialShading::parse(Dict *dict) { shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A, funcsA, nFuncsA, extend0A, extend1A); - if (!shading->init(dict)) { + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2413,7 +2494,7 @@ GfxGouraudTriangleShading::GfxGouraudTriangleShading( int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, - Function **funcsA, int nFuncsA): + int nCompsA, Function **funcsA, int nFuncsA): GfxShading(typeA) { int i; @@ -2422,6 +2503,7 @@ GfxGouraudTriangleShading::GfxGouraudTriangleShading( nVertices = nVerticesA; triangles = trianglesA; nTriangles = nTrianglesA; + nComps = nCompsA; nFuncs = nFuncsA; for (i = 0; i < nFuncs; ++i) { funcs[i] = funcsA[i]; @@ -2440,6 +2522,7 @@ GfxGouraudTriangleShading::GfxGouraudTriangleShading( nTriangles = shading->nTriangles; triangles = (int (*)[3])gmallocn(nTriangles * 3, sizeof(int)); memcpy(triangles, shading->triangles, nTriangles * 3 * sizeof(int)); + nComps = shading->nComps; nFuncs = shading->nFuncs; for (i = 0; i < nFuncs; ++i) { funcs[i] = shading->funcs[i]->copy(); @@ -2456,9 +2539,9 @@ GfxGouraudTriangleShading::~GfxGouraudTriangleShading() { } } -GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, - Dict *dict, - Stream *str) { +GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse( + int typeA, Dict *dict, Stream *str + ) { GfxGouraudTriangleShading *shading; Function *funcsA[gfxColorMaxComps]; int nFuncsA; @@ -2469,7 +2552,7 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, double cMul[gfxColorMaxComps]; GfxGouraudVertex *verticesA; int (*trianglesA)[3]; - int nComps, nVerticesA, nTrianglesA, vertSize, triSize; + int nCompsA, nVerticesA, nTrianglesA, vertSize, triSize; Guint x, y, flag; Guint c[gfxColorMaxComps]; GfxShadingBitBuf *bitBuf; @@ -2531,7 +2614,7 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, obj2.free(); cMul[i] = (cMax[i] - cMin[i]) / (double)((1 << compBits) - 1); } - nComps = i; + nCompsA = i; } else { error(errSyntaxError, -1, "Missing or invalid Decode array in shading dictionary"); @@ -2585,12 +2668,12 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, !bitBuf->getBits(coordBits, &y)) { break; } - for (i = 0; i < nComps; ++i) { + for (i = 0; i < nCompsA; ++i) { if (!bitBuf->getBits(compBits, &c[i])) { break; } } - if (i < nComps) { + if (i < nCompsA) { break; } if (nVerticesA == vertSize) { @@ -2600,9 +2683,8 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, } verticesA[nVerticesA].x = xMin + xMul * (double)x; verticesA[nVerticesA].y = yMin + yMul * (double)y; - for (i = 0; i < nComps; ++i) { - verticesA[nVerticesA].color.c[i] = - dblToCol(cMin[i] + cMul[i] * (double)c[i]); + for (i = 0; i < nCompsA; ++i) { + verticesA[nVerticesA].color[i] = cMin[i] + cMul[i] * (double)c[i]; } ++nVerticesA; bitBuf->flushBits(); @@ -2657,8 +2739,9 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA, trianglesA, nTrianglesA, - funcsA, nFuncsA); - if (!shading->init(dict)) { + nCompsA, funcsA, nFuncsA); + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2676,54 +2759,46 @@ GfxShading *GfxGouraudTriangleShading::copy() { void GfxGouraudTriangleShading::getTriangle( int i, - double *x0, double *y0, GfxColor *color0, - double *x1, double *y1, GfxColor *color1, - double *x2, double *y2, GfxColor *color2) { - double in; - double out[gfxColorMaxComps]; + double *x0, double *y0, double *color0, + double *x1, double *y1, double *color1, + double *x2, double *y2, double *color2) { int v, j; v = triangles[i][0]; *x0 = vertices[v].x; *y0 = vertices[v].y; - if (nFuncs > 0) { - in = colToDbl(vertices[v].color.c[0]); - for (j = 0; j < nFuncs; ++j) { - funcs[j]->transform(&in, &out[j]); - } - for (j = 0; j < gfxColorMaxComps; ++j) { - color0->c[j] = dblToCol(out[j]); - } - } else { - *color0 = vertices[v].color; + for (j = 0; j < nComps; ++j) { + color0[j] = vertices[v].color[j]; } v = triangles[i][1]; *x1 = vertices[v].x; *y1 = vertices[v].y; - if (nFuncs > 0) { - in = colToDbl(vertices[v].color.c[0]); - for (j = 0; j < nFuncs; ++j) { - funcs[j]->transform(&in, &out[j]); - } - for (j = 0; j < gfxColorMaxComps; ++j) { - color1->c[j] = dblToCol(out[j]); - } - } else { - *color1 = vertices[v].color; + for (j = 0; j < nComps; ++j) { + color1[j] = vertices[v].color[j]; } v = triangles[i][2]; *x2 = vertices[v].x; *y2 = vertices[v].y; + for (j = 0; j < nComps; ++j) { + color2[j] = vertices[v].color[j]; + } +} + +void GfxGouraudTriangleShading::getColor(double *in, GfxColor *out) { + double c[gfxColorMaxComps]; + int i; + if (nFuncs > 0) { - in = colToDbl(vertices[v].color.c[0]); - for (j = 0; j < nFuncs; ++j) { - funcs[j]->transform(&in, &out[j]); + for (i = 0; i < nFuncs; ++i) { + funcs[i]->transform(in, &c[i]); } - for (j = 0; j < gfxColorMaxComps; ++j) { - color2->c[j] = dblToCol(out[j]); + for (i = 0; i < colorSpace->getNComps(); ++i) { + out->c[i] = dblToCol(c[i]); } } else { - *color2 = vertices[v].color; + for (i = 0; i < nComps; ++i) { + out->c[i] = dblToCol(in[i]); + } } } @@ -2733,6 +2808,7 @@ void GfxGouraudTriangleShading::getTriangle( GfxPatchMeshShading::GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, + int nCompsA, Function **funcsA, int nFuncsA): GfxShading(typeA) { @@ -2740,6 +2816,7 @@ GfxPatchMeshShading::GfxPatchMeshShading(int typeA, patches = patchesA; nPatches = nPatchesA; + nComps = nCompsA; nFuncs = nFuncsA; for (i = 0; i < nFuncs; ++i) { funcs[i] = funcsA[i]; @@ -2754,6 +2831,7 @@ GfxPatchMeshShading::GfxPatchMeshShading(GfxPatchMeshShading *shading): nPatches = shading->nPatches; patches = (GfxPatch *)gmallocn(nPatches, sizeof(GfxPatch)); memcpy(patches, shading->patches, nPatches * sizeof(GfxPatch)); + nComps = shading->nComps; nFuncs = shading->nFuncs; for (i = 0; i < nFuncs; ++i) { funcs[i] = shading->funcs[i]->copy(); @@ -2770,7 +2848,8 @@ GfxPatchMeshShading::~GfxPatchMeshShading() { } GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, - Stream *str) { + Stream *str + ) { GfxPatchMeshShading *shading; Function *funcsA[gfxColorMaxComps]; int nFuncsA; @@ -2780,11 +2859,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, double xMul, yMul; double cMul[gfxColorMaxComps]; GfxPatch *patchesA, *p; - int nComps, nPatchesA, patchesSize, nPts, nColors; + int nCompsA, nPatchesA, patchesSize, nPts, nColors; Guint flag; double x[16], y[16]; Guint xi, yi; - GfxColorComp c[4][gfxColorMaxComps]; + double c[4][gfxColorMaxComps]; Guint ci; GfxShadingBitBuf *bitBuf; Object obj1, obj2; @@ -2833,7 +2912,7 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, obj2.free(); cMul[i] = (cMax[i] - cMin[i]) / (double)((1 << compBits) - 1); } - nComps = i; + nCompsA = i; } else { error(errSyntaxError, -1, "Missing or invalid Decode array in shading dictionary"); @@ -2907,13 +2986,13 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, break; } for (i = 0; i < nColors; ++i) { - for (j = 0; j < nComps; ++j) { + for (j = 0; j < nCompsA; ++j) { if (!bitBuf->getBits(compBits, &ci)) { break; } - c[i][j] = dblToCol(cMin[j] + cMul[j] * (double)ci); + c[i][j] = cMin[j] + cMul[j] * (double)ci; } - if (j < nComps) { + if (j < nCompsA) { break; } } @@ -2953,11 +3032,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][0] = y[10]; p->x[1][0] = x[11]; p->y[1][0] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = c[0][j]; - p->color[0][1].c[j] = c[1][j]; - p->color[1][1].c[j] = c[2][j]; - p->color[1][0].c[j] = c[3][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = c[0][j]; + p->color[0][1][j] = c[1][j]; + p->color[1][1][j] = c[2][j]; + p->color[1][0][j] = c[3][j]; } break; case 1: @@ -2985,11 +3064,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[0][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 2: @@ -3017,11 +3096,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 3: @@ -3049,11 +3128,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; - for (j = 0; j < nComps; ++j) { - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[0][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; } @@ -3092,11 +3171,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][2] = y[14]; p->x[2][1] = x[15]; p->y[2][1] = y[15]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = c[0][j]; - p->color[0][1].c[j] = c[1][j]; - p->color[1][1].c[j] = c[2][j]; - p->color[1][0].c[j] = c[3][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = c[0][j]; + p->color[0][1][j] = c[1][j]; + p->color[1][1][j] = c[2][j]; + p->color[1][0][j] = c[3][j]; } break; case 1: @@ -3132,11 +3211,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[0][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 2: @@ -3172,11 +3251,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 3: @@ -3212,11 +3291,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[0][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; } @@ -3273,8 +3352,9 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, } shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA, - funcsA, nFuncsA); - if (!shading->init(dict)) { + nCompsA, funcsA, nFuncsA); + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -3290,6 +3370,24 @@ GfxShading *GfxPatchMeshShading::copy() { return new GfxPatchMeshShading(this); } +void GfxPatchMeshShading::getColor(double *in, GfxColor *out) { + double c[gfxColorMaxComps]; + int i; + + if (nFuncs > 0) { + for (i = 0; i < nFuncs; ++i) { + funcs[i]->transform(in, &c[i]); + } + for (i = 0; i < colorSpace->getNComps(); ++i) { + out->c[i] = dblToCol(c[i]); + } + } else { + for (i = 0; i < nComps; ++i) { + out->c[i] = dblToCol(in[i]); + } + } +} + //------------------------------------------------------------------------ // GfxImageColorMap //------------------------------------------------------------------------ @@ -3310,7 +3408,11 @@ GfxImageColorMap::GfxImageColorMap(int bitsA, Object *decode, // bits per component and color space bits = bitsA; - maxPixel = (1 << bits) - 1; + if (bits <= 8) { + maxPixel = (1 << bits) - 1; + } else { + maxPixel = 0xff; + } colorSpace = colorSpaceA; // initialize @@ -3431,7 +3533,11 @@ GfxImageColorMap::GfxImageColorMap(GfxImageColorMap *colorMap) { lookup[k] = NULL; lookup2[k] = NULL; } - n = 1 << bits; + if (bits <= 8) { + n = 1 << bits; + } else { + n = 256; + } for (k = 0; k < nComps; ++k) { lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp)); memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp)); @@ -3521,7 +3627,11 @@ void GfxImageColorMap::getCMYK(Guchar *x, GfxCMYK *cmyk) { void GfxImageColorMap::getColor(Guchar *x, GfxColor *color) { int maxPixel, i; - maxPixel = (1 << bits) - 1; + if (bits <= 8) { + maxPixel = (1 << bits) - 1; + } else { + maxPixel = 0xff; + } for (i = 0; i < nComps; ++i) { color->c[i] = dblToCol(decodeLow[i] + (x[i] * decodeRange[i]) / maxPixel); } @@ -3811,7 +3921,8 @@ void GfxPath::offset(double dx, double dy) { //------------------------------------------------------------------------ GfxState::GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox, - int rotateA, GBool upsideDown) { + int rotateA, GBool upsideDown + ) { double kx, ky; hDPI = hDPIA; @@ -3861,8 +3972,8 @@ GfxState::GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox, pageHeight = ky * (py2 - py1); } - fillColorSpace = new GfxDeviceGrayColorSpace(); - strokeColorSpace = new GfxDeviceGrayColorSpace(); + fillColorSpace = GfxColorSpace::create(csDeviceGray); + strokeColorSpace = GfxColorSpace::create(csDeviceGray); fillColor.c[0] = 0; strokeColor.c[0] = 0; fillPattern = NULL; diff --git a/xpdf/GfxState.h b/xpdf/GfxState.h index 5d57de9..74ab5eb 100644 --- a/xpdf/GfxState.h +++ b/xpdf/GfxState.h @@ -140,7 +140,13 @@ public: virtual GfxColorSpaceMode getMode() = 0; // Construct a color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Object *csObj, int recursion = 0); + static GfxColorSpace *parse(Object *csObj, + int recursion = 0); + + // Construct a simple color space. The <mode> argument can be + // csDeviceGray, csDeviceRGB, or csDeviceCMYK. + static GfxColorSpace *create(GfxColorSpaceMode mode); + // Convert to gray, RGB, or CMYK. virtual void getGray(GfxColor *color, GfxGray *gray) = 0; @@ -381,7 +387,8 @@ public: virtual GfxColorSpaceMode getMode() { return csICCBased; } // Construct an ICCBased color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -418,7 +425,8 @@ public: virtual GfxColorSpaceMode getMode() { return csIndexed; } // Construct an Indexed color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -457,7 +465,8 @@ public: virtual GfxColorSpaceMode getMode() { return csSeparation; } // Construct a Separation color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -499,7 +508,8 @@ public: virtual GfxColorSpaceMode getMode() { return csDeviceN; } // Construct a DeviceN color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -542,7 +552,8 @@ public: virtual GfxColorSpaceMode getMode() { return csPattern; } // Construct a Pattern color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -570,7 +581,8 @@ public: GfxPattern(int typeA); virtual ~GfxPattern(); - static GfxPattern *parse(Object *obj); + static GfxPattern *parse(Object *objRef, Object *obj + ); virtual GfxPattern *copy() = 0; @@ -588,7 +600,7 @@ private: class GfxTilingPattern: public GfxPattern { public: - static GfxTilingPattern *parse(Object *patObj); + static GfxTilingPattern *parse(Object *patObjRef, Object *patObj); virtual ~GfxTilingPattern(); virtual GfxPattern *copy(); @@ -601,7 +613,7 @@ public: Dict *getResDict() { return resDict.isDict() ? resDict.getDict() : (Dict *)NULL; } double *getMatrix() { return matrix; } - Object *getContentStream() { return &contentStream; } + Object *getContentStreamRef() { return &contentStreamRef; } private: @@ -616,7 +628,7 @@ private: double xStep, yStep; Object resDict; double matrix[6]; - Object contentStream; + Object contentStreamRef; }; //------------------------------------------------------------------------ @@ -626,7 +638,8 @@ private: class GfxShadingPattern: public GfxPattern { public: - static GfxShadingPattern *parse(Object *patObj); + static GfxShadingPattern *parse(Object *patObj + ); virtual ~GfxShadingPattern(); virtual GfxPattern *copy(); @@ -653,7 +666,8 @@ public: GfxShading(GfxShading *shading); virtual ~GfxShading(); - static GfxShading *parse(Object *obj); + static GfxShading *parse(Object *obj + ); virtual GfxShading *copy() = 0; @@ -667,7 +681,8 @@ public: protected: - GBool init(Dict *dict); + GBool init(Dict *dict + ); int type; GfxColorSpace *colorSpace; @@ -691,7 +706,8 @@ public: GfxFunctionShading(GfxFunctionShading *shading); virtual ~GfxFunctionShading(); - static GfxFunctionShading *parse(Dict *dict); + static GfxFunctionShading *parse(Dict *dict + ); virtual GfxShading *copy(); @@ -725,7 +741,8 @@ public: GfxAxialShading(GfxAxialShading *shading); virtual ~GfxAxialShading(); - static GfxAxialShading *parse(Dict *dict); + static GfxAxialShading *parse(Dict *dict + ); virtual GfxShading *copy(); @@ -763,7 +780,8 @@ public: GfxRadialShading(GfxRadialShading *shading); virtual ~GfxRadialShading(); - static GfxRadialShading *parse(Dict *dict); + static GfxRadialShading *parse(Dict *dict + ); virtual GfxShading *copy(); @@ -793,7 +811,7 @@ private: struct GfxGouraudVertex { double x, y; - GfxColor color; + double color[gfxColorMaxComps]; }; class GfxGouraudTriangleShading: public GfxShading { @@ -802,18 +820,21 @@ public: GfxGouraudTriangleShading(int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, - Function **funcsA, int nFuncsA); + int nCompsA, Function **funcsA, int nFuncsA); GfxGouraudTriangleShading(GfxGouraudTriangleShading *shading); virtual ~GfxGouraudTriangleShading(); - static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str); + static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str + ); virtual GfxShading *copy(); + int getNComps() { return nComps; } int getNTriangles() { return nTriangles; } - void getTriangle(int i, double *x0, double *y0, GfxColor *color0, - double *x1, double *y1, GfxColor *color1, - double *x2, double *y2, GfxColor *color2); + void getTriangle(int i, double *x0, double *y0, double *color0, + double *x1, double *y1, double *color1, + double *x2, double *y2, double *color2); + void getColor(double *in, GfxColor *out); private: @@ -822,6 +843,7 @@ private: int (*triangles)[3]; int nTriangles; Function *funcs[gfxColorMaxComps]; + int nComps; // number of color components (1 if nFuncs > 0) int nFuncs; }; @@ -832,29 +854,33 @@ private: struct GfxPatch { double x[4][4]; double y[4][4]; - GfxColor color[2][2]; + double color[2][2][gfxColorMaxComps]; }; class GfxPatchMeshShading: public GfxShading { public: GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, - Function **funcsA, int nFuncsA); + int nCompsA, Function **funcsA, int nFuncsA); GfxPatchMeshShading(GfxPatchMeshShading *shading); virtual ~GfxPatchMeshShading(); - static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str); + static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str + ); virtual GfxShading *copy(); + int getNComps() { return nComps; } int getNPatches() { return nPatches; } GfxPatch *getPatch(int i) { return &patches[i]; } + void getColor(double *in, GfxColor *out); private: GfxPatch *patches; int nPatches; Function *funcs[gfxColorMaxComps]; + int nComps; // number of color components (1 if nFuncs > 0) int nFuncs; }; @@ -1040,7 +1066,8 @@ public: // x <vDPI>, page box <pageBox>, page rotation <rotateA>, and // coordinate system specified by <upsideDown>. GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox, - int rotateA, GBool upsideDown); + int rotateA, GBool upsideDown + ); // Destructor. ~GfxState(); diff --git a/xpdf/GlobalParams.cc b/xpdf/GlobalParams.cc index 63e932b..ea7e9fd 100644 --- a/xpdf/GlobalParams.cc +++ b/xpdf/GlobalParams.cc @@ -12,15 +12,19 @@ #pragma implementation #endif +#ifdef _WIN32 +# define _WIN32_WINNT 0x0500 // for GetSystemWindowsDirectory +# include <windows.h> +#endif #include <string.h> #include <stdio.h> #include <ctype.h> #ifdef ENABLE_PLUGINS -# ifndef WIN32 +# ifndef _WIN32 # include <dlfcn.h> # endif #endif -#ifdef WIN32 +#ifdef _WIN32 # include <shlobj.h> #endif #if HAVE_PAPER_H @@ -31,6 +35,7 @@ #include "GList.h" #include "GHash.h" #include "gfile.h" +#include "FoFiIdentifier.h" #include "Error.h" #include "NameToCharCode.h" #include "CharCodeToUnicode.h" @@ -43,8 +48,9 @@ #endif #include "GlobalParams.h" -#ifdef WIN32 +#ifdef _WIN32 # define strcasecmp stricmp +# define strncasecmp strnicmp #endif #if MULTITHREADED @@ -68,7 +74,7 @@ #include "UTF8.h" #ifdef ENABLE_PLUGINS -# ifdef WIN32 +# ifdef _WIN32 extern XpdfPluginVecTable xpdfPluginVecTable; # endif #endif @@ -84,25 +90,29 @@ static struct { const char *name; const char *t1FileName; const char *ttFileName; + const char *macFileName; // may be .dfont, .ttf, or .ttc + const char *macFontName; // font name inside .dfont or .ttc + const char *obliqueFont; // name of font to oblique + double obliqueFactor; // oblique sheer factor } displayFontTab[] = { - {"Courier", "n022003l.pfb", "cour.ttf"}, - {"Courier-Bold", "n022004l.pfb", "courbd.ttf"}, - {"Courier-BoldOblique", "n022024l.pfb", "courbi.ttf"}, - {"Courier-Oblique", "n022023l.pfb", "couri.ttf"}, - {"Helvetica", "n019003l.pfb", "arial.ttf"}, - {"Helvetica-Bold", "n019004l.pfb", "arialbd.ttf"}, - {"Helvetica-BoldOblique", "n019024l.pfb", "arialbi.ttf"}, - {"Helvetica-Oblique", "n019023l.pfb", "ariali.ttf"}, - {"Symbol", "s050000l.pfb", NULL}, - {"Times-Bold", "n021004l.pfb", "timesbd.ttf"}, - {"Times-BoldItalic", "n021024l.pfb", "timesbi.ttf"}, - {"Times-Italic", "n021023l.pfb", "timesi.ttf"}, - {"Times-Roman", "n021003l.pfb", "times.ttf"}, - {"ZapfDingbats", "d050000l.pfb", NULL}, + {"Courier", "n022003l.pfb", "cour.ttf", "Courier", "Courier", NULL, 0}, + {"Courier-Bold", "n022004l.pfb", "courbd.ttf", "Courier", "Courier Bold", NULL, 0}, + {"Courier-BoldOblique", "n022024l.pfb", "courbi.ttf", "Courier", "Courier Bold Oblique", "Courier-Bold", 0.212557}, + {"Courier-Oblique", "n022023l.pfb", "couri.ttf", "Courier", "Courier Oblique", "Courier", 0.212557}, + {"Helvetica", "n019003l.pfb", "arial.ttf", "Helvetica", "Helvetica", NULL, 0}, + {"Helvetica-Bold", "n019004l.pfb", "arialbd.ttf", "Helvetica", "Helvetica-Bold", NULL, 0}, + {"Helvetica-BoldOblique", "n019024l.pfb", "arialbi.ttf", "Helvetica", "Helvetica Bold Oblique", "Helvetica-Bold", 0.212557}, + {"Helvetica-Oblique", "n019023l.pfb", "ariali.ttf", "Helvetica", "Helvetica Oblique", "Helvetica", 0.212557}, + {"Symbol", "s050000l.pfb", NULL, "Symbol", "Symbol", NULL, 0}, + {"Times-Bold", "n021004l.pfb", "timesbd.ttf", "Times", "Times-Bold", NULL, 0}, + {"Times-BoldItalic", "n021024l.pfb", "timesbi.ttf", "Times", "Times-BoldItalic", NULL, 0}, + {"Times-Italic", "n021023l.pfb", "timesi.ttf", "Times", "Times-Italic", NULL, 0}, + {"Times-Roman", "n021003l.pfb", "times.ttf", "Times", "Times-Roman", NULL, 0}, + {"ZapfDingbats", "d050000l.pfb", NULL, "ZapfDingbats", "Zapf Dingbats", NULL, 0}, {NULL} }; -#ifdef WIN32 +#ifdef _WIN32 static const char *displayFontDirs[] = { "c:/windows/fonts", "c:/winnt/fonts", @@ -115,10 +125,31 @@ static const char *displayFontDirs[] = { "/usr/share/fonts/default/Type1", "/usr/share/fonts/default/ghostscript", "/usr/share/fonts/type1/gsfonts", +#if defined(__sun) && defined(__SVR4) + "/usr/sfw/share/ghostscript/fonts", +#endif NULL }; #endif +#ifdef __APPLE__ +static const char *macSystemFontPath = "/System/Library/Fonts"; +#endif + +struct Base14FontInfo { + Base14FontInfo(GString *fileNameA, int fontNumA, double obliqueA) { + fileName = fileNameA; + fontNum = fontNumA; + oblique = obliqueA; + } + ~Base14FontInfo() { + delete fileName; + } + GString *fileName; + int fontNum; + double oblique; +}; + //------------------------------------------------------------------------ GlobalParams *globalParams = NULL; @@ -198,13 +229,13 @@ public: ~SysFontList(); SysFontInfo *find(GString *name); -#ifdef WIN32 +#ifdef _WIN32 void scanWindowsFonts(char *winFontDir); #endif private: -#ifdef WIN32 +#ifdef _WIN32 SysFontInfo *makeWindowsFont(char *name, int fontNum, char *path); #endif @@ -241,40 +272,36 @@ SysFontInfo *SysFontList::find(GString *name) { } n = name2->getLength(); - // remove trailing "MT" (Foo-MT, Foo-BoldMT, etc.) - if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) { - name2->del(n - 2, 2); - n -= 2; - } + // font names like "Arial-BoldMT,Bold" are occasionally used, + // so run this loop twice + bold = italic = gFalse; + for (i = 0; i < 2; ++i) { - // look for "Regular" - if (n > 7 && !strcmp(name2->getCString() + n - 7, "Regular")) { - name2->del(n - 7, 7); - n -= 7; - } + // remove trailing "MT" (Foo-MT, Foo-BoldMT, etc.) + if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) { + name2->del(n - 2, 2); + n -= 2; + } - // look for "Italic" - if (n > 6 && !strcmp(name2->getCString() + n - 6, "Italic")) { - name2->del(n - 6, 6); - italic = gTrue; - n -= 6; - } else { - italic = gFalse; - } + // look for "Regular" + if (n > 7 && !strcmp(name2->getCString() + n - 7, "Regular")) { + name2->del(n - 7, 7); + n -= 7; + } - // look for "Bold" - if (n > 4 && !strcmp(name2->getCString() + n - 4, "Bold")) { - name2->del(n - 4, 4); - bold = gTrue; - n -= 4; - } else { - bold = gFalse; - } + // look for "Italic" + if (n > 6 && !strcmp(name2->getCString() + n - 6, "Italic")) { + name2->del(n - 6, 6); + italic = gTrue; + n -= 6; + } - // remove trailing "MT" (FooMT-Bold, etc.) - if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) { - name2->del(n - 2, 2); - n -= 2; + // look for "Bold" + if (n > 4 && !strcmp(name2->getCString() + n - 4, "Bold")) { + name2->del(n - 4, 4); + bold = gTrue; + n -= 4; + } } // remove trailing "PS" @@ -323,7 +350,7 @@ SysFontInfo *SysFontList::find(GString *name) { return fi; } -#ifdef WIN32 +#ifdef _WIN32 void SysFontList::scanWindowsFonts(char *winFontDir) { OSVERSIONINFO version; char *path; @@ -341,15 +368,15 @@ void SysFontList::scanWindowsFonts(char *winFontDir) { } else { path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts\\"; } - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, - KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, - ®Key) == ERROR_SUCCESS) { + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, + ®Key) == ERROR_SUCCESS) { idx = 0; while (1) { valNameLen = sizeof(valName) - 1; dataLen = sizeof(data) - 1; - if (RegEnumValue(regKey, idx, valName, &valNameLen, NULL, - &type, (LPBYTE)data, &dataLen) != ERROR_SUCCESS) { + if (RegEnumValueA(regKey, idx, valName, &valNameLen, NULL, + &type, (LPBYTE)data, &dataLen) != ERROR_SUCCESS) { break; } if (type == REG_SZ && @@ -357,7 +384,7 @@ void SysFontList::scanWindowsFonts(char *winFontDir) { dataLen > 0 && dataLen < sizeof(data)) { valName[valNameLen] = '\0'; data[dataLen] = '\0'; - n = strlen(data); + n = (int)strlen(data); if (!strcasecmp(data + n - 4, ".ttf") || !strcasecmp(data + n - 4, ".ttc")) { fontPath = new GString(data); @@ -398,7 +425,7 @@ SysFontInfo *SysFontList::makeWindowsFont(char *name, int fontNum, int i; SysFontType type; - n = strlen(name); + n = (int)strlen(name); bold = italic = gFalse; // remove trailing ' (TrueType)' @@ -419,7 +446,7 @@ SysFontInfo *SysFontList::makeWindowsFont(char *name, int fontNum, } // remove trailing ' Regular' - if (n > 5 && !strncmp(name + n - 8, " Regular", 8)) { + if (n > 8 && !strncmp(name + n - 8, " Regular", 8)) { n -= 8; } @@ -490,7 +517,7 @@ public: private: -#ifdef WIN32 +#ifdef _WIN32 Plugin(HMODULE libA); HMODULE lib; #else @@ -504,7 +531,7 @@ Plugin *Plugin::load(char *type, char *name) { Plugin *plugin; XpdfPluginVecTable *vt; XpdfBool (*xpdfInitPlugin)(void); -#ifdef WIN32 +#ifdef _WIN32 HMODULE libA; #else void *dlA; @@ -515,9 +542,9 @@ Plugin *Plugin::load(char *type, char *name) { appendToPath(path, type); appendToPath(path, name); -#ifdef WIN32 +#ifdef _WIN32 path->append(".dll"); - if (!(libA = LoadLibrary(path->getCString()))) { + if (!(libA = LoadLibraryA(path->getCString()))) { error(errIO, -1, "Failed to load plugin '{0:t}'", path); goto err1; } @@ -548,7 +575,7 @@ Plugin *Plugin::load(char *type, char *name) { } memcpy(vt, &xpdfPluginVecTable, sizeof(xpdfPluginVecTable)); -#ifdef WIN32 +#ifdef _WIN32 if (!(xpdfInitPlugin = (XpdfBool (*)(void)) GetProcAddress(libA, "xpdfInitPlugin"))) { error(errIO, -1, "Failed to find xpdfInitPlugin in plugin '{0:t}'", @@ -568,7 +595,7 @@ Plugin *Plugin::load(char *type, char *name) { goto err2; } -#ifdef WIN32 +#ifdef _WIN32 plugin = new Plugin(libA); #else plugin = new Plugin(dlA); @@ -578,7 +605,7 @@ Plugin *Plugin::load(char *type, char *name) { return plugin; err2: -#ifdef WIN32 +#ifdef _WIN32 FreeLibrary(libA); #else dlclose(dlA); @@ -588,7 +615,7 @@ Plugin *Plugin::load(char *type, char *name) { return NULL; } -#ifdef WIN32 +#ifdef _WIN32 Plugin::Plugin(HMODULE libA) { lib = libA; } @@ -601,7 +628,7 @@ Plugin::Plugin(void *dlA) { Plugin::~Plugin() { void (*xpdfFreePlugin)(void); -#ifdef WIN32 +#ifdef _WIN32 if ((xpdfFreePlugin = (void (*)(void)) GetProcAddress(lib, "xpdfFreePlugin"))) { (*xpdfFreePlugin)(); @@ -621,7 +648,7 @@ Plugin::~Plugin() { // parsing //------------------------------------------------------------------------ -GlobalParams::GlobalParams(char *cfgFileName) { +GlobalParams::GlobalParams(const char *cfgFileName) { UnicodeMap *map; GString *fileName; FILE *f; @@ -644,7 +671,7 @@ GlobalParams::GlobalParams(char *cfgFileName) { } } -#ifdef WIN32 +#ifdef _WIN32 // baseDir will be set by a call to setBaseDir baseDir = new GString(); #else @@ -660,6 +687,7 @@ GlobalParams::GlobalParams(char *cfgFileName) { fontFiles = new GHash(gTrue); fontDirs = new GList(); ccFontFiles = new GHash(gTrue); + base14SysFonts = new GHash(gTrue); sysFonts = new SysFontList(); #if HAVE_PAPER_H char *paperName; @@ -683,6 +711,7 @@ GlobalParams::GlobalParams(char *cfgFileName) { psImageableURX = psPaperWidth; psImageableURY = psPaperHeight; psCrop = gTrue; + psUseCropBoxAsPage = gFalse; psExpandSmaller = gFalse; psShrinkLarger = gTrue; psCenter = gTrue; @@ -700,12 +729,15 @@ GlobalParams::GlobalParams(char *cfgFileName) { psPreload = gFalse; psOPI = gFalse; psASCIIHex = gFalse; + psLZW = gTrue; psUncompressPreloadedImages = gFalse; + psMinLineWidth = 0; psRasterResolution = 300; psRasterMono = gFalse; + psRasterSliceSize = 20000000; psAlwaysRasterize = gFalse; textEncoding = new GString("Latin1"); -#if defined(WIN32) +#if defined(_WIN32) textEOL = eolDOS; #elif defined(MACOS) textEOL = eolMac; @@ -713,10 +745,9 @@ GlobalParams::GlobalParams(char *cfgFileName) { textEOL = eolUnix; #endif textPageBreaks = gTrue; - textKeepTinyChars = gFalse; + textKeepTinyChars = gTrue; initialZoom = new GString("125"); continuousView = gFalse; - enableT1lib = gTrue; enableFreeType = gTrue; disableFreeTypeHinting = gFalse; antialias = gTrue; @@ -737,6 +768,8 @@ GlobalParams::GlobalParams(char *cfgFileName) { movieCommand = NULL; mapNumericCharNames = gTrue; mapUnknownCharNames = gFalse; + mapExtTrueTypeFontsViaUnicode = gTrue; + enableXFA = gTrue; createDefaultKeyBindings(); printCommands = gFalse; errQuiet = gFalse; @@ -791,9 +824,9 @@ GlobalParams::GlobalParams(char *cfgFileName) { } } if (!f) { -#ifdef WIN32 +#ifdef _WIN32 char buf[512]; - i = GetModuleFileName(NULL, buf, sizeof(buf)); + i = GetModuleFileNameA(NULL, buf, sizeof(buf)); if (i <= 0 || i >= sizeof(buf)) { // error or path too long for buffer - just use the current dir buf[0] = '\0'; @@ -928,7 +961,7 @@ void GlobalParams::createDefaultKeyBindings() { keyBindings->append(new KeyBinding('l', xpdfKeyModCtrl, xpdfKeyContextAny, "redraw")); keyBindings->append(new KeyBinding('w', xpdfKeyModCtrl, - xpdfKeyContextAny, "closeWindow")); + xpdfKeyContextAny, "closeWindowOrQuit")); keyBindings->append(new KeyBinding('?', xpdfKeyModNone, xpdfKeyContextAny, "about")); keyBindings->append(new KeyBinding('q', xpdfKeyModNone, @@ -1017,6 +1050,9 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { parsePSImageableArea(tokens, fileName, line); } else if (!cmd->cmp("psCrop")) { parseYesNo("psCrop", &psCrop, tokens, fileName, line); + } else if (!cmd->cmp("psUseCropBoxAsPage")) { + parseYesNo("psUseCropBoxAsPage", &psUseCropBoxAsPage, + tokens, fileName, line); } else if (!cmd->cmp("psExpandSmaller")) { parseYesNo("psExpandSmaller", &psExpandSmaller, tokens, fileName, line); @@ -1054,14 +1090,22 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { parseYesNo("psOPI", &psOPI, tokens, fileName, line); } else if (!cmd->cmp("psASCIIHex")) { parseYesNo("psASCIIHex", &psASCIIHex, tokens, fileName, line); + } else if (!cmd->cmp("psLZW")) { + parseYesNo("psLZW", &psLZW, tokens, fileName, line); } else if (!cmd->cmp("psUncompressPreloadedImages")) { parseYesNo("psUncompressPreloadedImages", &psUncompressPreloadedImages, tokens, fileName, line); + } else if (!cmd->cmp("psMinLineWidth")) { + parseFloat("psMinLineWidth", &psMinLineWidth, + tokens, fileName, line); } else if (!cmd->cmp("psRasterResolution")) { parseFloat("psRasterResolution", &psRasterResolution, tokens, fileName, line); } else if (!cmd->cmp("psRasterMono")) { parseYesNo("psRasterMono", &psRasterMono, tokens, fileName, line); + } else if (!cmd->cmp("psRasterSliceSize")) { + parseInteger("psRasterSliceSize", &psRasterSliceSize, + tokens, fileName, line); } else if (!cmd->cmp("psAlwaysRasterize")) { parseYesNo("psAlwaysRasterize", &psAlwaysRasterize, tokens, fileName, line); @@ -1079,8 +1123,6 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { parseInitialZoom(tokens, fileName, line); } else if (!cmd->cmp("continuousView")) { parseYesNo("continuousView", &continuousView, tokens, fileName, line); - } else if (!cmd->cmp("enableT1lib")) { - parseYesNo("enableT1lib", &enableT1lib, tokens, fileName, line); } else if (!cmd->cmp("enableFreeType")) { parseYesNo("enableFreeType", &enableFreeType, tokens, fileName, line); } else if (!cmd->cmp("disableFreeTypeHinting")) { @@ -1133,6 +1175,12 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { } else if (!cmd->cmp("mapUnknownCharNames")) { parseYesNo("mapUnknownCharNames", &mapUnknownCharNames, tokens, fileName, line); + } else if (!cmd->cmp("mapExtTrueTypeFontsViaUnicode")) { + parseYesNo("mapExtTrueTypeFontsViaUnicode", + &mapExtTrueTypeFontsViaUnicode, + tokens, fileName, line); + } else if (!cmd->cmp("enableXFA")) { + parseYesNo("enableXFA", &enableXFA, tokens, fileName, line); } else if (!cmd->cmp("bind")) { parseBind(tokens, fileName, line); } else if (!cmd->cmp("unbind")) { @@ -1148,6 +1196,8 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { !cmd->cmp("displayNamedCIDFontX") || !cmd->cmp("displayCIDFontX")) { error(errConfig, -1, "Xpdf no longer supports X fonts"); + } else if (!cmd->cmp("enableT1lib")) { + error(errConfig, -1, "Xpdf no longer uses t1lib"); } else if (!cmd->cmp("t1libControl") || !cmd->cmp("freetypeControl")) { error(errConfig, -1, "The t1libControl and freetypeControl options have been replaced by the enableT1lib, enableFreeType, and antialias options"); @@ -1520,6 +1570,7 @@ void GlobalParams::parseScreenType(GList *tokens, GString *fileName, } } + void GlobalParams::parseBind(GList *tokens, GString *fileName, int line) { KeyBinding *binding; GList *cmds; @@ -1836,6 +1887,7 @@ GlobalParams::~GlobalParams() { deleteGHash(fontFiles, GString); deleteGList(fontDirs, GString); deleteGHash(ccFontFiles, GString); + deleteGHash(base14SysFonts, Base14FontInfo); delete sysFonts; if (psFile) { delete psFile; @@ -1886,35 +1938,77 @@ void GlobalParams::setBaseDir(char *dir) { baseDir = new GString(dir); } -void GlobalParams::setupBaseFonts(char *dir) { - GString *fontName; - GString *fileName; -#ifdef WIN32 +#ifdef _WIN32 +static void getWinFontDir(char *winFontDir) { HMODULE shell32Lib; BOOL (__stdcall *SHGetSpecialFolderPathFunc)(HWND hwndOwner, - LPTSTR lpszPath, + LPSTR lpszPath, int nFolder, BOOL fCreate); - char winFontDir[MAX_PATH]; -#endif - FILE *f; - int i, j; + char *p; + int i; -#ifdef WIN32 // SHGetSpecialFolderPath isn't available in older versions of // shell32.dll (Win95 and WinNT4), so do a dynamic load winFontDir[0] = '\0'; - if ((shell32Lib = LoadLibrary("shell32.dll"))) { + if ((shell32Lib = LoadLibraryA("shell32.dll"))) { if ((SHGetSpecialFolderPathFunc = - (BOOL (__stdcall *)(HWND hwndOwner, LPTSTR lpszPath, + (BOOL (__stdcall *)(HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate)) GetProcAddress(shell32Lib, "SHGetSpecialFolderPathA"))) { if (!(*SHGetSpecialFolderPathFunc)(NULL, winFontDir, CSIDL_FONTS, FALSE)) { winFontDir[0] = '\0'; } + // kludge: Terminal Server changes CSIDL_FONTS to something like + // "C:\Users\whatever\Windows\Fonts", which doesn't actually + // contain any fonts -- kill that, so we hit the fallback code + // below. + for (p = winFontDir; *p; ++p) { + if (!strncasecmp(p, "\\Users\\", 7)) { + winFontDir[0] = '\0'; + break; + } + } + } + } + // if something went wrong, or we're on a Terminal Server, try using + // %SYSTEMROOT%\Fonts + if (!winFontDir[0]) { + GetSystemWindowsDirectoryA(winFontDir, MAX_PATH - 6); + winFontDir[MAX_PATH - 7] = '\0'; + i = (int)strlen(winFontDir); + if (winFontDir[i-1] != '\\') { + winFontDir[i++] = '\\'; } + strcpy(winFontDir + i, "Fonts"); } +} +#endif + +void GlobalParams::setupBaseFonts(char *dir) { + GString *fontName; + GString *fileName; + int fontNum; + const char *s; + Base14FontInfo *base14; +#ifdef _WIN32 + char winFontDir[MAX_PATH]; +#endif +#ifdef __APPLE__ + static const char *macFontExts[3] = { "dfont", "ttc", "ttf" }; + GList *dfontFontNames; + GBool found; + int k; +#endif + FILE *f; + int i, j; + +#ifdef _WIN32 + getWinFontDir(winFontDir); +#endif +#ifdef __APPLE__ + dfontFontNames = NULL; #endif for (i = 0; displayFontTab[i].name; ++i) { if (fontFiles->lookup(displayFontTab[i].name)) { @@ -1922,6 +2016,7 @@ void GlobalParams::setupBaseFonts(char *dir) { } fontName = new GString(displayFontTab[i].name); fileName = NULL; + fontNum = 0; if (dir) { fileName = appendToPath(new GString(dir), displayFontTab[i].t1FileName); if ((f = fopen(fileName->getCString(), "rb"))) { @@ -1931,7 +2026,7 @@ void GlobalParams::setupBaseFonts(char *dir) { fileName = NULL; } } -#ifdef WIN32 +#ifdef _WIN32 if (!fileName && winFontDir[0] && displayFontTab[i].ttFileName) { fileName = appendToPath(new GString(winFontDir), displayFontTab[i].ttFileName); @@ -1942,13 +2037,67 @@ void GlobalParams::setupBaseFonts(char *dir) { fileName = NULL; } } - // SHGetSpecialFolderPath(CSIDL_FONTS) doesn't work on Win 2k Server - // or Win2003 Server, or with older versions of shell32.dll, so check - // the "standard" directories - if (displayFontTab[i].ttFileName) { +#endif +#ifdef __APPLE__ + // Check for Mac OS X system fonts. + s = displayFontTab[i].macFileName; + if (dfontFontNames && i > 0 && + (!s || strcmp(s, displayFontTab[i-1].macFileName))) { + deleteGList(dfontFontNames, GString); + dfontFontNames = NULL; + } + if (!fileName && s) { + for (j = 0; j < 3; ++j) { + fileName = GString::format("{0:s}/{1:s}.{2:s}", + macSystemFontPath, s, macFontExts[j]); + if (!(f = fopen(fileName->getCString(), "rb"))) { + delete fileName; + fileName = NULL; + } else { + fclose(f); + found = gFalse; + // for .dfont or .ttc, we need to scan the font list + if (j < 2) { + if (!dfontFontNames) { + dfontFontNames = + FoFiIdentifier::getFontList(fileName->getCString()); + } + if (dfontFontNames) { + for (k = 0; k < dfontFontNames->getLength(); ++k) { + if (!((GString *)dfontFontNames->get(k)) + ->cmp(displayFontTab[i].macFontName)) { + fontNum = k; + found = gTrue; + break; + } + } + } + // for .ttf, we just use the font + } else { + found = gTrue; + } + if (!found) { + delete fileName; + fileName = NULL; + } + break; + } + } + } +#endif // __APPLE__ + // On Linux, this checks the "standard" ghostscript font + // directories. On Windows, it checks the "standard" system font + // directories (because SHGetSpecialFolderPath(CSIDL_FONTS) + // doesn't work on Win 2k Server or Win2003 Server, or with older + // versions of shell32.dll). +#ifdef _WIN32 + s = displayFontTab[i].ttFileName; +#else + s = displayFontTab[i].t1FileName; +#endif + if (!fileName && s) { for (j = 0; !fileName && displayFontDirs[j]; ++j) { - fileName = appendToPath(new GString(displayFontDirs[j]), - displayFontTab[i].ttFileName); + fileName = appendToPath(new GString(displayFontDirs[j]), s); if ((f = fopen(fileName->getCString(), "rb"))) { fclose(f); } else { @@ -1957,28 +2106,35 @@ void GlobalParams::setupBaseFonts(char *dir) { } } } -#else // WIN32 - for (j = 0; !fileName && displayFontDirs[j]; ++j) { - fileName = appendToPath(new GString(displayFontDirs[j]), - displayFontTab[i].t1FileName); - if ((f = fopen(fileName->getCString(), "rb"))) { - fclose(f); - } else { - delete fileName; - fileName = NULL; - } - } -#endif // WIN32 if (!fileName) { - error(errConfig, -1, "No display font for '{0:s}'", - displayFontTab[i].name); delete fontName; continue; } - addFontFile(fontName, fileName); + base14SysFonts->add(fontName, new Base14FontInfo(fileName, fontNum, 0)); } - -#ifdef WIN32 +#ifdef __APPLE__ + if (dfontFontNames) { + deleteGList(dfontFontNames, GString); + } +#endif + for (i = 0; displayFontTab[i].name; ++i) { + if (!base14SysFonts->lookup(displayFontTab[i].name) && + !fontFiles->lookup(displayFontTab[i].name)) { + if (displayFontTab[i].obliqueFont && + ((base14 = (Base14FontInfo *)base14SysFonts + ->lookup(displayFontTab[i].obliqueFont)))) { + base14SysFonts->add( + new GString(displayFontTab[i].name), + new Base14FontInfo(base14->fileName->copy(), + base14->fontNum, + displayFontTab[i].obliqueFactor)); + } else { + error(errConfig, -1, "No display font for '{0:s}'", + displayFontTab[i].name); + } + } + } +#ifdef _WIN32 if (winFontDir[0]) { sysFonts->scanWindowsFonts(winFontDir); } @@ -2083,7 +2239,7 @@ FILE *GlobalParams::findToUnicodeFile(GString *name) { GString *GlobalParams::findFontFile(GString *fontName) { static const char *exts[] = { ".pfa", ".pfb", ".ttf", ".ttc" }; GString *path, *dir; -#ifdef WIN32 +#ifdef _WIN32 GString *fontNameU; #endif const char *ext; @@ -2100,7 +2256,7 @@ GString *GlobalParams::findFontFile(GString *fontName) { dir = (GString *)fontDirs->get(i); for (j = 0; j < (int)(sizeof(exts) / sizeof(exts[0])); ++j) { ext = exts[j]; -#ifdef WIN32 +#ifdef _WIN32 fontNameU = fileNameToUTF8(fontName->getCString()); path = appendToPath(dir->copy(), fontNameU->getCString()); delete fontNameU; @@ -2120,6 +2276,25 @@ GString *GlobalParams::findFontFile(GString *fontName) { return NULL; } +GString *GlobalParams::findBase14FontFile(GString *fontName, int *fontNum, + double *oblique) { + Base14FontInfo *fi; + GString *path; + + lockGlobalParams; + if ((fi = (Base14FontInfo *)base14SysFonts->lookup(fontName))) { + path = fi->fileName->copy(); + *fontNum = fi->fontNum; + *oblique = fi->oblique; + unlockGlobalParams; + return path; + } + unlockGlobalParams; + *fontNum = 0; + *oblique = 0; + return findFontFile(fontName); +} + GString *GlobalParams::findSystemFontFile(GString *fontName, SysFontType *type, int *fontNum) { @@ -2193,6 +2368,15 @@ GBool GlobalParams::getPSCrop() { return f; } +GBool GlobalParams::getPSUseCropBoxAsPage() { + GBool f; + + lockGlobalParams; + f = psUseCropBoxAsPage; + unlockGlobalParams; + return f; +} + GBool GlobalParams::getPSExpandSmaller() { GBool f; @@ -2242,7 +2426,9 @@ GString *GlobalParams::getPSResidentFont(GString *fontName) { GString *psName; lockGlobalParams; - psName = (GString *)psResidentFonts->lookup(fontName); + if ((psName = (GString *)psResidentFonts->lookup(fontName))) { + psName = psName->copy(); + } unlockGlobalParams; return psName; } @@ -2371,6 +2557,15 @@ GBool GlobalParams::getPSASCIIHex() { return ah; } +GBool GlobalParams::getPSLZW() { + GBool ah; + + lockGlobalParams; + ah = psLZW; + unlockGlobalParams; + return ah; +} + GBool GlobalParams::getPSUncompressPreloadedImages() { GBool ah; @@ -2380,6 +2575,15 @@ GBool GlobalParams::getPSUncompressPreloadedImages() { return ah; } +double GlobalParams::getPSMinLineWidth() { + double w; + + lockGlobalParams; + w = psMinLineWidth; + unlockGlobalParams; + return w; +} + double GlobalParams::getPSRasterResolution() { double res; @@ -2398,6 +2602,15 @@ GBool GlobalParams::getPSRasterMono() { return mono; } +int GlobalParams::getPSRasterSliceSize() { + int slice; + + lockGlobalParams; + slice = psRasterSliceSize; + unlockGlobalParams; + return slice; +} + GBool GlobalParams::getPSAlwaysRasterize() { GBool rast; @@ -2461,15 +2674,6 @@ GBool GlobalParams::getContinuousView() { return f; } -GBool GlobalParams::getEnableT1lib() { - GBool f; - - lockGlobalParams; - f = enableT1lib; - unlockGlobalParams; - return f; -} - GBool GlobalParams::getEnableFreeType() { GBool f; @@ -2597,6 +2801,7 @@ GBool GlobalParams::getDrawAnnotations() { return draw; } + GBool GlobalParams::getMapNumericCharNames() { GBool map; @@ -2615,6 +2820,24 @@ GBool GlobalParams::getMapUnknownCharNames() { return map; } +GBool GlobalParams::getMapExtTrueTypeFontsViaUnicode() { + GBool map; + + lockGlobalParams; + map = mapExtTrueTypeFontsViaUnicode; + unlockGlobalParams; + return map; +} + +GBool GlobalParams::getEnableXFA() { + GBool enable; + + lockGlobalParams; + enable = enableXFA; + unlockGlobalParams; + return enable; +} + GList *GlobalParams::getKeyBinding(int code, int mods, int context) { KeyBinding *binding; GList *cmds; @@ -2804,6 +3027,12 @@ void GlobalParams::setPSCrop(GBool crop) { unlockGlobalParams; } +void GlobalParams::setPSUseCropBoxAsPage(GBool crop) { + lockGlobalParams; + psUseCropBoxAsPage = crop; + unlockGlobalParams; +} + void GlobalParams::setPSExpandSmaller(GBool expand) { lockGlobalParams; psExpandSmaller = expand; @@ -2882,7 +3111,7 @@ void GlobalParams::setPSASCIIHex(GBool hex) { unlockGlobalParams; } -void GlobalParams::setTextEncoding(char *encodingName) { +void GlobalParams::setTextEncoding(const char *encodingName) { lockGlobalParams; delete textEncoding; textEncoding = new GString(encodingName); @@ -2930,15 +3159,6 @@ void GlobalParams::setContinuousView(GBool cont) { unlockGlobalParams; } -GBool GlobalParams::setEnableT1lib(char *s) { - GBool ok; - - lockGlobalParams; - ok = parseYesNo2(s, &enableT1lib); - unlockGlobalParams; - return ok; -} - GBool GlobalParams::setEnableFreeType(char *s) { GBool ok; @@ -3015,6 +3235,18 @@ void GlobalParams::setMapUnknownCharNames(GBool map) { unlockGlobalParams; } +void GlobalParams::setMapExtTrueTypeFontsViaUnicode(GBool map) { + lockGlobalParams; + mapExtTrueTypeFontsViaUnicode = map; + unlockGlobalParams; +} + +void GlobalParams::setEnableXFA(GBool enable) { + lockGlobalParams; + enableXFA = enable; + unlockGlobalParams; +} + void GlobalParams::setPrintCommands(GBool printCommandsA) { lockGlobalParams; printCommands = printCommandsA; diff --git a/xpdf/GlobalParams.h b/xpdf/GlobalParams.h index e2da5ca..07a6f83 100644 --- a/xpdf/GlobalParams.h +++ b/xpdf/GlobalParams.h @@ -173,7 +173,7 @@ public: // Initialize the global parameters by attempting to read a config // file. - GlobalParams(char *cfgFileName); + GlobalParams(const char *cfgFileName); ~GlobalParams(); @@ -193,6 +193,8 @@ public: FILE *findCMapFile(GString *collection, GString *cMapName); FILE *findToUnicodeFile(GString *name); GString *findFontFile(GString *fontName); + GString *findBase14FontFile(GString *fontName, int *fontNum, + double *oblique); GString *findSystemFontFile(GString *fontName, SysFontType *type, int *fontNum); GString *findCCFontFile(GString *collection); @@ -202,6 +204,7 @@ public: void getPSImageableArea(int *llx, int *lly, int *urx, int *ury); GBool getPSDuplex(); GBool getPSCrop(); + GBool getPSUseCropBoxAsPage(); GBool getPSExpandSmaller(); GBool getPSShrinkLarger(); GBool getPSCenter(); @@ -218,9 +221,12 @@ public: GBool getPSPreload(); GBool getPSOPI(); GBool getPSASCIIHex(); + GBool getPSLZW(); GBool getPSUncompressPreloadedImages(); + double getPSMinLineWidth(); double getPSRasterResolution(); GBool getPSRasterMono(); + int getPSRasterSliceSize(); GBool getPSAlwaysRasterize(); GString *getTextEncodingName(); EndOfLineKind getTextEOL(); @@ -228,7 +234,6 @@ public: GBool getTextKeepTinyChars(); GString *getInitialZoom(); GBool getContinuousView(); - GBool getEnableT1lib(); GBool getEnableFreeType(); GBool getDisableFreeTypeHinting(); GBool getAntialias(); @@ -249,6 +254,8 @@ public: GString *getMovieCommand() { return movieCommand; } GBool getMapNumericCharNames(); GBool getMapUnknownCharNames(); + GBool getMapExtTrueTypeFontsViaUnicode(); + GBool getEnableXFA(); GList *getKeyBinding(int code, int mods, int context); GBool getPrintCommands(); GBool getErrQuiet(); @@ -269,6 +276,7 @@ public: void setPSImageableArea(int llx, int lly, int urx, int ury); void setPSDuplex(GBool duplex); void setPSCrop(GBool crop); + void setPSUseCropBoxAsPage(GBool crop); void setPSExpandSmaller(GBool expand); void setPSShrinkLarger(GBool shrink); void setPSCenter(GBool center); @@ -281,13 +289,12 @@ public: void setPSPreload(GBool preload); void setPSOPI(GBool opi); void setPSASCIIHex(GBool hex); - void setTextEncoding(char *encodingName); + void setTextEncoding(const char *encodingName); GBool setTextEOL(char *s); void setTextPageBreaks(GBool pageBreaks); void setTextKeepTinyChars(GBool keep); void setInitialZoom(char *s); void setContinuousView(GBool cont); - GBool setEnableT1lib(char *s); GBool setEnableFreeType(char *s); GBool setAntialias(char *s); GBool setVectorAntialias(char *s); @@ -299,6 +306,8 @@ public: void setScreenWhiteThreshold(double thresh); void setMapNumericCharNames(GBool map); void setMapUnknownCharNames(GBool map); + void setMapExtTrueTypeFontsViaUnicode(GBool map); + void setEnableXFA(GBool enable); void setPrintCommands(GBool printCommandsA); void setErrQuiet(GBool errQuietA); @@ -379,6 +388,8 @@ private: GList *fontDirs; // list of font dirs [GString] GHash *ccFontFiles; // character collection font files: // collection name mapped to path [GString] + GHash *base14SysFonts; // Base-14 system font files: font name + // mapped to path [Base14FontInfo] SysFontList *sysFonts; // system fonts GString *psFile; // PostScript file or command (for xpdf) int psPaperWidth; // paper size, in PostScript points, for @@ -388,6 +399,7 @@ private: psImageableURX, psImageableURY; GBool psCrop; // crop PS output to CropBox + GBool psUseCropBoxAsPage; // use CropBox as page size GBool psExpandSmaller; // expand smaller pages to fill paper GBool psShrinkLarger; // shrink larger pages to fit paper GBool psCenter; // center pages on the paper @@ -411,11 +423,15 @@ private: // memory GBool psOPI; // generate PostScript OPI comments? GBool psASCIIHex; // use ASCIIHex instead of ASCII85? + GBool psLZW; // false to use RLE instead of LZW GBool psUncompressPreloadedImages; // uncompress all preloaded images + double psMinLineWidth; // minimum line width for PostScript output double psRasterResolution; // PostScript rasterization resolution (dpi) GBool psRasterMono; // true to do PostScript rasterization // in monochrome (gray); false to do it // in color (RGB/CMYK) + int psRasterSliceSize; // maximum size (pixels) of PostScript + // rasterization slice GBool psAlwaysRasterize; // force PostScript rasterization GString *textEncoding; // encoding (unicodeMap) to use for text // output @@ -425,7 +441,6 @@ private: GBool textKeepTinyChars; // keep all characters in text output GString *initialZoom; // initial zoom level GBool continuousView; // continuous view mode - GBool enableT1lib; // t1lib enable flag GBool enableFreeType; // FreeType enable flag GBool disableFreeTypeHinting; // FreeType hinting disable flag GBool antialias; // font anti-aliasing enable flag @@ -446,6 +461,9 @@ private: GString *movieCommand; // command executed for movie annotations GBool mapNumericCharNames; // map numeric char names (from font subsets)? GBool mapUnknownCharNames; // map unknown char names? + GBool mapExtTrueTypeFontsViaUnicode; // map char codes to GID via Unicode + // for external TrueType fonts? + GBool enableXFA; // enable XFA form rendering GList *keyBindings; // key & mouse button bindings [KeyBinding] GBool printCommands; // print the drawing commands GBool errQuiet; // suppress error messages? diff --git a/xpdf/HTMLGen.cc b/xpdf/HTMLGen.cc new file mode 100644 index 0000000..6372e13 --- /dev/null +++ b/xpdf/HTMLGen.cc @@ -0,0 +1,583 @@ +//======================================================================== +// +// HTMLGen.cc +// +// Copyright 2010 Glyph & Cog, LLC +// +//======================================================================== + +//~ to do: +//~ - fonts +//~ - underlined? (underlines are present in the background image) +//~ - include the original font name in the CSS entry (before the +//~ generic serif/sans-serif/monospace name) +//~ - check that htmlDir exists and is a directory +//~ - links: +//~ - links to pages +//~ - links to named destinations +//~ - links to URLs +//~ - rotated text should go in the background image +//~ - metadata +//~ - PDF outline + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <png.h> +#include "gmem.h" +#include "GString.h" +#include "GList.h" +#include "SplashBitmap.h" +#include "PDFDoc.h" +#include "TextOutputDev.h" +#include "SplashOutputDev.h" +#include "ErrorCodes.h" +#if EVAL_MODE +# include "SplashMath.h" +# include "Splash.h" +# include "BuiltinFontTables.h" +# include "FontEncodingTables.h" +#endif +#include "HTMLGen.h" + +#ifdef _WIN32 +# define strcasecmp stricmp +# define strncasecmp strnicmp +#endif + +//------------------------------------------------------------------------ + +struct FontStyleTagInfo { + const char *tag; + int tagLen; + GBool bold; + GBool italic; +}; + +// NB: these are compared, in order, against the tail of the font +// name, so "BoldItalic" must come before "Italic", etc. +static FontStyleTagInfo fontStyleTags[] = { + {"Roman", 5, gFalse, gFalse}, + {"Regular", 7, gFalse, gFalse}, + {"Condensed", 9, gFalse, gFalse}, + {"CondensedBold", 13, gTrue, gFalse}, + {"CondensedLight", 14, gFalse, gFalse}, + {"SemiBold", 8, gTrue, gFalse}, + {"BoldItalic", 10, gTrue, gTrue}, + {"Bold_Italic", 11, gTrue, gTrue}, + {"BoldOblique", 11, gTrue, gTrue}, + {"Bold_Oblique", 12, gTrue, gTrue}, + {"Bold", 4, gTrue, gFalse}, + {"Italic", 6, gFalse, gTrue}, + {"Oblique", 7, gFalse, gTrue}, + {NULL, 0, gFalse, gFalse} +}; + +struct StandardFontInfo { + const char *name; + GBool fixedWidth; + GBool serif; +}; + +static StandardFontInfo standardFonts[] = { + {"Arial", gFalse, gFalse}, + {"Courier", gTrue, gFalse}, + {"Futura", gFalse, gFalse}, + {"Helvetica", gFalse, gFalse}, + {"Minion", gFalse, gTrue}, + {"NewCenturySchlbk", gFalse, gTrue}, + {"Times", gFalse, gTrue}, + {"TimesNew", gFalse, gTrue}, + {"Times_New", gFalse, gTrue}, + {"Verdana", gFalse, gFalse}, + {"LucidaSans", gFalse, gFalse}, + {NULL, gFalse, gFalse} +}; + +struct SubstFontInfo { + double mWidth; +}; + +// index: {fixed:8, serif:4, sans-serif:0} + bold*2 + italic +static SubstFontInfo substFonts[16] = { + {0.833}, + {0.833}, + {0.889}, + {0.889}, + {0.788}, + {0.722}, + {0.833}, + {0.778}, + {0.600}, + {0.600}, + {0.600}, + {0.600} +}; + +// Map Unicode indexes from the private use area, following the Adobe +// Glyph list. +#define privateUnicodeMapStart 0xf6f9 +#define privateUnicodeMapEnd 0xf7ff +static int +privateUnicodeMap[privateUnicodeMapEnd - privateUnicodeMapStart + 1] = { + 0x0141, 0x0152, 0, 0, 0x0160, 0, 0x017d, // f6f9 + 0, 0, 0, 0, 0, 0, 0, 0, // f700 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f710 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x0021, 0, 0, 0x0024, 0, 0x0026, 0, // f720 + 0, 0, 0, 0, 0, 0, 0, 0, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, // f730 + 0x0038, 0x0039, 0, 0, 0, 0, 0, 0x003f, + 0, 0, 0, 0, 0, 0, 0, 0, // f740 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f750 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, // f760 + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, // f770 + 0x0058, 0x0059, 0x005a, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f780 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f790 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x00a1, 0x00a2, 0, 0, 0, 0, 0, // f7a0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f7b0 + 0, 0, 0, 0, 0, 0, 0, 0x00bf, + 0, 0, 0, 0, 0, 0, 0, 0, // f7c0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f7d0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, // f7e0 + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0, // f7f0 + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x0178 +}; + +//------------------------------------------------------------------------ + +#if EVAL_MODE + +#define EVAL_MODE_MSG "XpdfHTML evaluation - www.glyphandcog.com" + +static void drawEvalModeMsg(SplashOutputDev *out) { + BuiltinFont *bf; + SplashFont *font; + GString *fontName; + char *msg; + SplashCoord mat[4], ident[6]; + SplashCoord diag, size, textW, x, y; + Gushort cw; + int w, h, n, i; + + // get the Helvetica font info + bf = builtinFontSubst[4]; + + msg = EVAL_MODE_MSG; + n = strlen(msg); + + w = out->getBitmap()->getWidth(); + h = out->getBitmap()->getHeight(); + + ident[0] = 1; ident[1] = 0; + ident[2] = 0; ident[3] = -1; + ident[4] = 0; ident[5] = h; + out->getSplash()->setMatrix(ident); + + diag = splashSqrt((SplashCoord)(w*w + h*h)); + size = diag / (0.67 * n); + if (size < 8) { + size = 8; + } + mat[0] = size * (SplashCoord)w / diag; + mat[3] = mat[0]; + mat[1] = size * (SplashCoord)h / diag; + mat[2] = -mat[1]; + fontName = new GString(bf->name); + font = out->getFont(fontName, mat); + delete fontName; + if (!font) { + return; + } + + textW = 0; + for (i = 0; i < n; ++i) { + bf->widths->getWidth(winAnsiEncoding[msg[i] & 0xff], &cw); + textW += size * cw * 0.001; + } + + out->setFillColor(255, 0, 0); + x = 0.5 * (diag - textW) * (SplashCoord)w / diag; + y = 0.5 * (diag - textW) * (SplashCoord)h / diag; + for (i = 0; i < n; ++i) { + out->getSplash()->fillChar(x, y, msg[i], font); + bf->widths->getWidth(winAnsiEncoding[msg[i] & 0xff], &cw); + x += mat[0] * cw * 0.001; + y += mat[1] * cw * 0.001; + } +} +#endif + +//------------------------------------------------------------------------ + +HTMLGen::HTMLGen(double backgroundResolutionA) { + TextOutputControl textOutControl; + SplashColor paperColor; + + ok = gTrue; + + backgroundResolution = backgroundResolutionA; + drawInvisibleText = gTrue; + + // set up the TextOutputDev + textOutControl.mode = textOutReadingOrder; + textOutControl.html = gTrue; + textOut = new TextOutputDev(NULL, &textOutControl, gFalse); + if (!textOut->isOk()) { + ok = gFalse; + } + + // set up the SplashOutputDev + paperColor[0] = paperColor[1] = paperColor[2] = 0xff; + splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse, paperColor); + splashOut->setSkipText(gTrue, gFalse); +} + +HTMLGen::~HTMLGen() { + delete textOut; + delete splashOut; +} + +void HTMLGen::startDoc(PDFDoc *docA) { + doc = docA; + splashOut->startDoc(doc->getXRef()); +} + +static inline int pr(int (*writeFunc)(void *stream, const char *data, int size), + void *stream, const char *data) { + return writeFunc(stream, data, (int)strlen(data)); +} + +static int pf(int (*writeFunc)(void *stream, const char *data, int size), + void *stream, const char *fmt, ...) { + va_list args; + GString *s; + int ret; + + va_start(args, fmt); + s = GString::formatv(fmt, args); + va_end(args); + ret = writeFunc(stream, s->getCString(), s->getLength()); + delete s; + return ret; +} + +struct PNGWriteInfo { + int (*writePNG)(void *stream, const char *data, int size); + void *pngStream; +}; + +static void pngWriteFunc(png_structp png, png_bytep data, png_size_t size) { + PNGWriteInfo *info; + + info = (PNGWriteInfo *)png_get_progressive_ptr(png); + info->writePNG(info->pngStream, (char *)data, (int)size); +} + +int HTMLGen::convertPage( + int pg, const char *pngURL, + int (*writeHTML)(void *stream, const char *data, int size), + void *htmlStream, + int (*writePNG)(void *stream, const char *data, int size), + void *pngStream) { + png_structp png; + png_infop pngInfo; + PNGWriteInfo writeInfo; + SplashBitmap *bitmap; + Guchar *p; + double pageW, pageH; + TextPage *text; + GList *fonts, *cols, *pars, *lines, *words; + double *fontScales; + TextFontInfo *font; + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word0, *word1; + GString *s; + double base, base1; + int subSuper0, subSuper1; + double r0, g0, b0, r1, g1, b1; + int colIdx, parIdx, lineIdx, wordIdx; + int y, i, u; + + // generate the background bitmap + doc->displayPage(splashOut, pg, backgroundResolution, backgroundResolution, + 0, gFalse, gTrue, gFalse); +#if EVAL_MODE + drawEvalModeMsg(splashOut); +#endif + bitmap = splashOut->getBitmap(); + if (!(png = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL)) || + !(pngInfo = png_create_info_struct(png))) { + return errFileIO; + } + if (setjmp(png_jmpbuf(png))) { + return errFileIO; + } + writeInfo.writePNG = writePNG; + writeInfo.pngStream = pngStream; + png_set_write_fn(png, &writeInfo, pngWriteFunc, NULL); + png_set_IHDR(png, pngInfo, bitmap->getWidth(), bitmap->getHeight(), + 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, pngInfo); + p = bitmap->getDataPtr(); + for (y = 0; y < bitmap->getHeight(); ++y) { + png_write_row(png, (png_bytep)p); + p += bitmap->getRowSize(); + } + png_write_end(png, pngInfo); + png_destroy_write_struct(&png, &pngInfo); + + // page size + pageW = doc->getPageCropWidth(pg); + pageH = doc->getPageCropHeight(pg); + + // get the PDF text + doc->displayPage(textOut, pg, 72, 72, 0, gFalse, gTrue, gFalse); + doc->processLinks(textOut, pg); + text = textOut->takeText(); + + // HTML header + pr(writeHTML, htmlStream, "<html>\n"); + pr(writeHTML, htmlStream, "<head>\n"); + pr(writeHTML, htmlStream, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); + pr(writeHTML, htmlStream, "<style type=\"text/css\">\n"); + pr(writeHTML, htmlStream, ".txt { white-space:nowrap; }\n"); + fonts = text->getFonts(); + fontScales = (double *)gmallocn(fonts->getLength(), sizeof(double)); + for (i = 0; i < fonts->getLength(); ++i) { + font = (TextFontInfo *)fonts->get(i); + s = getFontDefn(font, &fontScales[i]); + pf(writeHTML, htmlStream, "#f{0:d} {{ {1:t} }}\n", i, s); + delete s; + } + pr(writeHTML, htmlStream, "</style>\n"); + pr(writeHTML, htmlStream, "</head>\n"); + pr(writeHTML, htmlStream, "<body onload=\"start()\">\n"); + pf(writeHTML, htmlStream, "<img id=\"background\" style=\"position:absolute; left:0px; top:0px;\" width=\"{0:d}\" height=\"{1:d}\" src=\"{2:s}\">\n", + (int)pageW, (int)pageH, pngURL); + + // generate the HTML text + cols = text->makeColumns(); + for (colIdx = 0; colIdx < cols->getLength(); ++colIdx) { + col = (TextColumn *)cols->get(colIdx); + pars = col->getParagraphs(); + for (parIdx = 0; parIdx < pars->getLength(); ++parIdx) { + par = (TextParagraph *)pars->get(parIdx); + lines = par->getLines(); + for (lineIdx = 0; lineIdx < lines->getLength(); ++lineIdx) { + line = (TextLine *)lines->get(lineIdx); + if (line->getRotation() != 0) { + continue; + } + words = line->getWords(); + base = line->getBaseline(); + s = new GString(); + word0 = NULL; + subSuper0 = 0; // make gcc happy + r0 = g0 = b0 = 0; // make gcc happy + for (wordIdx = 0; wordIdx < words->getLength(); ++wordIdx) { + word1 = (TextWord *)words->get(wordIdx); + if (!drawInvisibleText && word1->isInvisible()) { + continue; + } + word1->getColor(&r1, &g1, &b1); + base1 = word1->getBaseline(); + if (base1 - base < -1) { + subSuper1 = -1; // superscript + } else if (base1 - base > 1) { + subSuper1 = 1; // subscript + } else { + subSuper1 = 0; + } + if (!word0 || + word1->getFontInfo() != word0->getFontInfo() || + word1->getFontSize() != word0->getFontSize() || + subSuper1 != subSuper0 || + r1 != r0 || g1 != g0 || b1 != b0) { + if (word0) { + s->append("</span>"); + } + for (i = 0; i < fonts->getLength(); ++i) { + if (word1->getFontInfo() == (TextFontInfo *)fonts->get(i)) { + break; + } + } + s->appendf("<span id=\"f{0:d}\" style=\"font-size:{1:d}px;vertical-align:{2:s};color:#{3:02x}{4:02x}{5:02x};\">", + i, (int)(fontScales[i] * word1->getFontSize()), + subSuper1 < 0 ? "super" + : subSuper1 > 0 ? "sub" + : "baseline", + (int)(r1 * 255), (int)(g1 * 255), (int)(b1 * 255)); + } + for (i = 0; i < word1->getLength(); ++i) { + u = word1->getChar(i); + if (u >= privateUnicodeMapStart && + u <= privateUnicodeMapEnd && + privateUnicodeMap[u - privateUnicodeMapStart]) { + u = privateUnicodeMap[u - privateUnicodeMapStart]; + } + if (u <= 0x7f) { + if (u == '&') { + s->append("&"); + } else if (u == '<') { + s->append("<"); + } else if (u == '>') { + s->append(">"); + } else { + s->append((char)u); + } + } else if (u <= 0x7ff) { + s->append((char)(0xc0 + (u >> 6))); + s->append((char)(0x80 + (u & 0x3f))); + } else if (u <= 0xffff) { + s->append((char)0xe0 + (u >> 12)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } else if (u <= 0x1fffff) { + s->append((char)0xf0 + (u >> 18)); + s->append((char)0x80 + ((u >> 12) & 0x3f)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } else if (u <= 0x3ffffff) { + s->append((char)0xf8 + (u >> 24)); + s->append((char)0x80 + ((u >> 18) & 0x3f)); + s->append((char)0x80 + ((u >> 12) & 0x3f)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } else if (u <= 0x7fffffff) { + s->append((char)0xfc + (u >> 30)); + s->append((char)0x80 + ((u >> 24) & 0x3f)); + s->append((char)0x80 + ((u >> 18) & 0x3f)); + s->append((char)0x80 + ((u >> 12) & 0x3f)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } + } + if (word1->getSpaceAfter()) { + s->append(' '); + } + word0 = word1; + subSuper0 = subSuper1; + r0 = r1; + g0 = g1; + b0 = b1; + } + s->append("</span>"); + pf(writeHTML, htmlStream, "<div class=\"txt\" style=\"position:absolute; left:{0:d}px; top:{1:d}px;\">{2:t}</div>\n", + (int)line->getXMin(), (int)line->getYMin(), s); + delete s; + } + } + } + gfree(fontScales); + delete text; + deleteGList(cols, TextColumn); + + // HTML trailer + pr(writeHTML, htmlStream, "</body>\n"); + pr(writeHTML, htmlStream, "</html>\n"); + + return errNone; +} + +GString *HTMLGen::getFontDefn(TextFontInfo *font, double *scale) { + GString *fontName; + char *fontName2; + FontStyleTagInfo *fst; + StandardFontInfo *sf; + GBool fixedWidth, serif, bold, italic; + double s; + int n, i; + + // get the font name, remove any subset tag + fontName = font->getFontName(); + if (fontName) { + fontName2 = fontName->getCString(); + n = fontName->getLength(); + for (i = 0; i < n && i < 7; ++i) { + if (fontName2[i] < 'A' || fontName2[i] > 'Z') { + break; + } + } + if (i == 6 && n > 7 && fontName2[6] == '+') { + fontName2 += 7; + n -= 7; + } + } else { + fontName2 = NULL; + n = 0; + } + + // get the style info from the font descriptor flags + fixedWidth = font->isFixedWidth(); + serif = font->isSerif(); + bold = font->isBold(); + italic = font->isItalic(); + + if (fontName2) { + + // look for a style tag at the end of the font name -- this + // overrides the font descriptor bold/italic flags + for (fst = fontStyleTags; fst->tag; ++fst) { + if (n > fst->tagLen && + !strcasecmp(fontName2 + n - fst->tagLen, fst->tag)) { + bold = fst->bold; + italic = fst->italic; + n -= fst->tagLen; + if (n > 1 && (fontName2[n-1] == '-' || + fontName2[n-1] == ',' || + fontName2[n-1] == '.' || + fontName2[n-1] == '_')) { + --n; + } + break; + } + } + + // look for a known font name -- this overrides the font descriptor + // fixedWidth/serif flags + for (sf = standardFonts; sf->name; ++sf) { + if (!strncasecmp(fontName2, sf->name, n)) { + fixedWidth = sf->fixedWidth; + serif = sf->serif; + break; + } + } + } + + // compute the scaling factor + *scale = 1; + if ((s = font->getMWidth())) { + i = (fixedWidth ? 8 : serif ? 4 : 0) + (bold ? 2 : 0) + (italic ? 1 : 0); + if (s < substFonts[i].mWidth) { + *scale = s / substFonts[i].mWidth; + } + } + + // generate the CSS markup + return GString::format("font-family:{0:s}; font-weight:{1:s}; font-style:{2:s};", + fixedWidth ? "monospace" + : serif ? "serif" + : "sans-serif", + bold ? "bold" : "normal", + italic ? "italic" : "normal"); +} diff --git a/xpdf/HTMLGen.h b/xpdf/HTMLGen.h new file mode 100644 index 0000000..7271c67 --- /dev/null +++ b/xpdf/HTMLGen.h @@ -0,0 +1,63 @@ +//======================================================================== +// +// HTMLGen.h +// +// Copyright 2010 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef HTMLGEN_H +#define HTMLGEN_H + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +class GString; +class PDFDoc; +class TextOutputDev; +class TextFontInfo; +class SplashOutputDev; + +//------------------------------------------------------------------------ + +class HTMLGen { +public: + + HTMLGen(double backgroundResolutionA); + ~HTMLGen(); + + GBool isOk() { return ok; } + + double getBackgroundResolution() { return backgroundResolution; } + void setBackgroundResolution(double backgroundResolutionA) + { backgroundResolution = backgroundResolutionA; } + + GBool getDrawInvisibleText() { return drawInvisibleText; } + void setDrawInvisibleText(GBool drawInvisibleTextA) + { drawInvisibleText = drawInvisibleTextA; } + + void startDoc(PDFDoc *docA); + int convertPage(int pg, const char *pngURL, + int (*writeHTML)(void *stream, const char *data, int size), + void *htmlStream, + int (*writePNG)(void *stream, const char *data, int size), + void *pngStream); + +private: + + GString *getFontDefn(TextFontInfo *font, double *scale); + + double backgroundResolution; + GBool drawInvisibleText; + + PDFDoc *doc; + TextOutputDev *textOut; + SplashOutputDev *splashOut; + + GBool ok; +}; + +#endif diff --git a/xpdf/ImageOutputDev.cc b/xpdf/ImageOutputDev.cc index a5c9314..3f6b1a1 100644 --- a/xpdf/ImageOutputDev.cc +++ b/xpdf/ImageOutputDev.cc @@ -37,7 +37,8 @@ ImageOutputDev::~ImageOutputDev() { gfree(fileRoot); } -void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, +void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, + Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -47,16 +48,16 @@ void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, GBool interpolate) { FILE *f; - int c; - int size, i; + char buf[4096]; + int size, n, i; // dump JPEG file if (dumpJPEG && str->getKind() == strDCT && !inlineImg) { // open the image file - sprintf(fileName, "%s-%03d.jpg", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -68,8 +69,9 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, str->reset(); // copy the stream - while ((c = str->getChar()) != EOF) - fputc(c, f); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); + } str->close(); fclose(f); @@ -78,7 +80,7 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, } else { // open the image file and write the PBM header - sprintf(fileName, "%s-%03d.pbm", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -92,8 +94,14 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, // copy the stream size = height * ((width + 7) / 8); - for (i = 0; i < size; ++i) { - fputc(str->getChar(), f); + while (size > 0) { + i = size < (int)sizeof(buf) ? size : (int)sizeof(buf); + n = str->getBlock(buf, i); + fwrite(buf, 1, n, f); + if (n < i) { + break; + } + size -= n; } str->close(); @@ -104,14 +112,15 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { FILE *f; ImageStream *imgStr; Guchar *p; GfxRGB rgb; int x, y; - int c; - int size, i; + char buf[4096]; + int size, n, i; // dump JPEG file if (dumpJPEG && str->getKind() == strDCT && @@ -120,7 +129,7 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, !inlineImg) { // open the image file - sprintf(fileName, "%s-%03d.jpg", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -132,8 +141,9 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, str->reset(); // copy the stream - while ((c = str->getChar()) != EOF) - fputc(c, f); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); + } str->close(); fclose(f); @@ -143,7 +153,7 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, colorMap->getBits() == 1) { // open the image file and write the PBM header - sprintf(fileName, "%s-%03d.pbm", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -157,8 +167,14 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, // copy the stream size = height * ((width + 7) / 8); - for (i = 0; i < size; ++i) { - fputc(str->getChar() ^ 0xff, f); + while (size > 0) { + i = size < (int)sizeof(buf) ? size : (int)sizeof(buf); + n = str->getBlock(buf, i); + fwrite(buf, 1, n, f); + if (n < i) { + break; + } + size -= n; } str->close(); @@ -168,7 +184,7 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, } else { // open the image file and write the PPM header - sprintf(fileName, "%s-%03d.ppm", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.ppm", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -203,8 +219,36 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, } } } + + imgStr->close(); delete imgStr; fclose(f); } } + +void ImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert, GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, + NULL, gFalse, interpolate); + drawImageMask(state, ref, maskStr, maskWidth, maskHeight, maskInvert, + gFalse, interpolate); +} + +void ImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, + Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + Stream *maskStr, + int maskWidth, int maskHeight, + GfxImageColorMap *maskColorMap, + GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, + NULL, gFalse, interpolate); + drawImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, + NULL, gFalse, interpolate); +} diff --git a/xpdf/ImageOutputDev.h b/xpdf/ImageOutputDev.h index 5496225..2984686 100644 --- a/xpdf/ImageOutputDev.h +++ b/xpdf/ImageOutputDev.h @@ -62,7 +62,7 @@ public: virtual GBool useDrawChar() { return gFalse; } //----- path painting - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -71,10 +71,22 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); + virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + Stream *maskStr, int maskWidth, int maskHeight, + GBool maskInvert, GBool interpolate); + virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + Stream *maskStr, + int maskWidth, int maskHeight, + GfxImageColorMap *maskColorMap, + GBool interpolate); private: diff --git a/xpdf/JArithmeticDecoder.cc b/xpdf/JArithmeticDecoder.cc index 56ddc31..92db326 100644 --- a/xpdf/JArithmeticDecoder.cc +++ b/xpdf/JArithmeticDecoder.cc @@ -92,10 +92,18 @@ JArithmeticDecoder::JArithmeticDecoder() { dataLen = 0; limitStream = gFalse; nBytesRead = 0; + readBuf = -1; } inline Guint JArithmeticDecoder::readByte() { + Guint x; + if (limitStream) { + if (readBuf >= 0) { + x = (Guint)readBuf; + readBuf = -1; + return x; + } --dataLen; if (dataLen < 0) { return 0xff; @@ -162,9 +170,15 @@ void JArithmeticDecoder::restart(int dataLenA) { void JArithmeticDecoder::cleanup() { if (limitStream) { + // This saves one extra byte of data from the end of packet i, to + // be used in packet i+1. It's not clear from the JPEG 2000 spec + // exactly how this should work, but this kludge does seem to fix + // decode of some problematic JPEG 2000 streams. It may actually + // be necessary to buffer an arbitrary number of bytes (not just + // one byte), but I haven't run into that case yet. while (dataLen > 0) { - buf0 = buf1; - buf1 = readByte(); + readBuf = -1; + readBuf = readByte(); } } } diff --git a/xpdf/JArithmeticDecoder.h b/xpdf/JArithmeticDecoder.h index 7217984..c0f773f 100644 --- a/xpdf/JArithmeticDecoder.h +++ b/xpdf/JArithmeticDecoder.h @@ -108,6 +108,7 @@ private: Guint nBytesRead; int dataLen; GBool limitStream; + int readBuf; }; #endif diff --git a/xpdf/JBIG2Stream.cc b/xpdf/JBIG2Stream.cc index eb2719a..8588931 100644 --- a/xpdf/JBIG2Stream.cc +++ b/xpdf/JBIG2Stream.cc @@ -623,11 +623,11 @@ Guint JBIG2MMRDecoder::get24Bits() { } void JBIG2MMRDecoder::skipTo(Guint length) { - while (nBytesRead < length) { - str->getChar(); - ++nBytesRead; - ++byteCounter; - } + int n; + + n = str->discardChars(length - nBytesRead); + nBytesRead += n; + byteCounter += n; } //------------------------------------------------------------------------ @@ -1310,10 +1310,9 @@ void JBIG2Stream::readSegments() { } refFlags = (refFlags << 24) | (c1 << 16) | (c2 << 8) | c3; nRefSegs = refFlags & 0x1fffffff; - for (i = 0; i < (nRefSegs + 9) >> 3; ++i) { - if ((c1 = curStr->getChar()) == EOF) { - goto eofError1; - } + i = (nRefSegs + 9) >> 3; + if (curStr->discardChars(i) != i) { + goto eofError1; } } @@ -1436,10 +1435,8 @@ void JBIG2Stream::readSegments() { break; default: error(errSyntaxError, getPos(), "Unknown segment type in JBIG2 stream"); - for (i = 0; i < segLength; ++i) { - if ((c1 = curStr->getChar()) == EOF) { - goto eofError2; - } + if (curStr->discardChars(segLength) != segLength) { + goto eofError2; } break; } @@ -1460,12 +1457,7 @@ void JBIG2Stream::readSegments() { gfree(refSegs); break; } - while (byteCounter < segLength) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(segLength - byteCounter); } gfree(refSegs); @@ -1502,9 +1494,8 @@ GBool JBIG2Stream::readSymbolDictSeg(Guint segNum, Guint length, Guint symHeight, symWidth, totalWidth, x, symID; int dh, dw, refAggNum, refDX, refDY, bmSize; GBool ex; - int run, cnt, c; + int run, cnt; Guint i, j, k; - Guchar *p; symWidths = NULL; @@ -1814,14 +1805,8 @@ GBool JBIG2Stream::readSymbolDictSeg(Guint segNum, Guint length, if (bmSize == 0) { collBitmap = new JBIG2Bitmap(0, totalWidth, symHeight); bmSize = symHeight * ((totalWidth + 7) >> 3); - p = collBitmap->getDataPtr(); - for (k = 0; k < (Guint)bmSize; ++k) { - if ((c = curStr->getChar()) == EOF) { - break; - } - *p++ = (Guchar)c; - ++byteCounter; - } + byteCounter += curStr->getBlock((char *)collBitmap->getDataPtr(), + bmSize); } else { collBitmap = readGenericBitmap(gTrue, totalWidth, symHeight, 0, gFalse, gFalse, NULL, NULL, NULL, @@ -2781,7 +2766,6 @@ JBIG2Bitmap *JBIG2Stream::readGenericBitmap(GBool mmr, int w, int h, Guchar mask; int x, y, x0, x1, a0i, b1i, blackPixels, pix, i; - bitmap = new JBIG2Bitmap(0, w, h); bitmap->clearToZero(); @@ -2994,7 +2978,7 @@ JBIG2Bitmap *JBIG2Stream::readGenericBitmap(GBool mmr, int w, int h, ltpCX = 0x0e3; // 001 1100 01 1 break; case 3: - ltpCX = 0x18a; // 01100 0101 1 + ltpCX = 0x18b; // 01100 0101 1 break; } } @@ -3821,27 +3805,13 @@ void JBIG2Stream::readPageInfoSeg(Guint length) { } void JBIG2Stream::readEndOfStripeSeg(Guint length) { - Guint i; - // skip the segment - for (i = 0; i < length; ++i) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(length); } void JBIG2Stream::readProfilesSeg(Guint length) { - Guint i; - // skip the segment - for (i = 0; i < length; ++i) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(length); } void JBIG2Stream::readCodeTableSeg(Guint segNum, Guint length) { @@ -3909,15 +3879,8 @@ void JBIG2Stream::readCodeTableSeg(Guint segNum, Guint length) { } void JBIG2Stream::readExtensionSeg(Guint length) { - Guint i; - // skip the segment - for (i = 0; i < length; ++i) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(length); } JBIG2Segment *JBIG2Stream::findSegment(Guint segNum) { diff --git a/xpdf/JPXStream.cc b/xpdf/JPXStream.cc index 98dcfef..7e2bc6b 100644 --- a/xpdf/JPXStream.cc +++ b/xpdf/JPXStream.cc @@ -42,7 +42,7 @@ #define jpxContextSigProp 0 // 0 - 8: significance prop and cleanup #define jpxContextSign 9 // 9 - 13: sign -#define jpxContextMagRef 14 // 14 -16: magnitude refinement +#define jpxContextMagRef 14 // 14 - 16: magnitude refinement #define jpxContextRunLength 17 // cleanup: run length #define jpxContextUniform 18 // cleanup: first signif coeff @@ -152,9 +152,10 @@ static Guint signContext[5][5][2] = { #define idwtKappa 1.230174104914001 #define idwtIKappa (1.0 / idwtKappa) -// number of bits to the right of the decimal point for the fixed -// point arithmetic used in the IDWT -#define fracBits 16 +// sum of the sample size (number of bits) and the number of bits to +// the right of the decimal point for the fixed point arithmetic used +// in the IDWT +#define fracBits 24 //------------------------------------------------------------------------ @@ -233,12 +234,25 @@ JPXStream::JPXStream(Stream *strA): nComps = 0; bpc = NULL; width = height = 0; + reduction = 0; haveCS = gFalse; + + palette.bpc = NULL; + palette.c = NULL; havePalette = gFalse; + + compMap.comp = NULL; + compMap.type = NULL; + compMap.pComp = NULL; haveCompMap = gFalse; + + channelDefn.idx = NULL; + channelDefn.type = NULL; + channelDefn.assoc = NULL; haveChannelDefn = gFalse; img.tiles = NULL; + bitBuf = 0; bitBufLen = 0; bitBufSkip = gFalse; @@ -252,13 +266,13 @@ JPXStream::~JPXStream() { void JPXStream::reset() { bufStr->reset(); - if (readBoxes()) { - curY = img.yOffset; - } else { + if (readBoxes() == jpxDecodeFatalError) { // readBoxes reported an error, so we go immediately to EOF - curY = img.ySize; + curY = img.ySizeR; + } else { + curY = img.yOffsetR; } - curX = img.xOffset; + curX = img.xOffsetR; curComp = 0; readBufLen = 0; } @@ -387,23 +401,25 @@ int JPXStream::lookChar() { void JPXStream::fillReadBuf() { JPXTileComp *tileComp; Guint tileIdx, tx, ty; - int pix, pixBits; + int pix, pixBits, k; + GBool eol; do { - if (curY >= img.ySize) { + if (curY >= img.ySizeR) { return; } - tileIdx = ((curY - img.yTileOffset) / img.yTileSize) * img.nXTiles - + (curX - img.xTileOffset) / img.xTileSize; + tileIdx = ((curY - img.yTileOffsetR) / img.yTileSizeR) * img.nXTiles + + (curX - img.xTileOffsetR) / img.xTileSizeR; #if 1 //~ ignore the palette, assume the PDF ColorSpace object is valid tileComp = &img.tiles[tileIdx].tileComps[curComp]; #else tileComp = &img.tiles[tileIdx].tileComps[havePalette ? 0 : curComp]; #endif - tx = jpxCeilDiv((curX - img.xTileOffset) % img.xTileSize, tileComp->hSep); - ty = jpxCeilDiv((curY - img.yTileOffset) % img.yTileSize, tileComp->vSep); - pix = (int)tileComp->data[ty * (tileComp->x1 - tileComp->x0) + tx]; + tx = jpxCeilDiv((curX - img.xTileOffsetR) % img.xTileSizeR, tileComp->hSep); + ty = jpxCeilDiv((curY - img.yTileOffsetR) % img.yTileSizeR, tileComp->vSep); + pix = (int)tileComp->data[ty * tileComp->w + tx]; pixBits = tileComp->prec; + eol = gFalse; #if 1 //~ ignore the palette, assume the PDF ColorSpace object is valid if (++curComp == img.nComps) { #else @@ -418,13 +434,10 @@ void JPXStream::fillReadBuf() { if (++curComp == (Guint)(havePalette ? palette.nComps : img.nComps)) { #endif curComp = 0; - if (++curX == img.xSize) { - curX = img.xOffset; + if (++curX == img.xSizeR) { + curX = img.xOffsetR; ++curY; - if (pixBits < 8) { - pix <<= 8 - pixBits; - pixBits = 8; - } + eol = gTrue; } } if (pixBits == 8) { @@ -433,6 +446,10 @@ void JPXStream::fillReadBuf() { readBuf = (readBuf << pixBits) | (pix & ((1 << pixBits) - 1)); } readBufLen += pixBits; + if (eol && (k = readBufLen & 7)) { + readBuf <<= 8 - k; + readBufLen += 8 - k; + } } while (readBufLen < 8); } @@ -447,7 +464,7 @@ GBool JPXStream::isBinary(GBool last) { void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode) { Guint boxType, boxLen, dataLen, csEnum; - Guint bpc1, dummy, i; + Guint bpc1, dummy; int csMeth, csPrec, csPrec1, dummy2; StreamColorSpaceMode csMode1; GBool haveBPC, haveCSMode; @@ -498,13 +515,13 @@ void JPXStream::getImageParams(int *bitsPerComponent, csPrec = csPrec1; haveCSMode = gTrue; } - for (i = 0; i < dataLen - 7; ++i) { - bufStr->getChar(); + if (dataLen > 7) { + bufStr->discardChars(dataLen - 7); } } } else { - for (i = 0; i < dataLen - 3; ++i) { - bufStr->getChar(); + if (dataLen > 3) { + bufStr->discardChars(dataLen - 3); } } } @@ -516,9 +533,7 @@ void JPXStream::getImageParams(int *bitsPerComponent, break; } else { cover(4); - for (i = 0; i < dataLen; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(dataLen); } } } @@ -529,7 +544,7 @@ void JPXStream::getImageParams(int *bitsPerComponent, void JPXStream::getImageParams2(int *bitsPerComponent, StreamColorSpaceMode *csMode) { int segType; - Guint segLen, nComps1, bpc1, dummy, i; + Guint segLen, nComps1, bpc1, dummy; while (readMarkerHdr(&segType, &segLen)) { if (segType == 0x51) { // SIZ - image and tile size @@ -559,15 +574,14 @@ void JPXStream::getImageParams2(int *bitsPerComponent, } else { cover(6); if (segLen > 2) { - for (i = 0; i < segLen - 2; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(segLen - 2); } } } } -GBool JPXStream::readBoxes() { +JPXDecodeResult JPXStream::readBoxes() { + JPXDecodeResult result; Guint boxType, boxLen, dataLen; Guint bpc1, compression, unknownColorspace, ipr; Guint i, j; @@ -581,8 +595,8 @@ GBool JPXStream::readBoxes() { cover(7); error(errSyntaxWarning, getPos(), "Naked JPEG 2000 codestream, missing JP2/JPX wrapper"); - if (!readCodestream(0)) { - return gFalse; + if ((result = readCodestream(0)) == jpxDecodeFatalError) { + return result; } nComps = img.nComps; bpc = (Guint *)gmallocn(nComps, sizeof(Guint)); @@ -591,7 +605,7 @@ GBool JPXStream::readBoxes() { } width = img.xSize - img.xOffset; height = img.ySize - img.yOffset; - return gTrue; + return result; } while (readBoxHdr(&boxType, &boxLen, &dataLen)) { @@ -614,12 +628,12 @@ GBool JPXStream::readBoxes() { !readUByte(&unknownColorspace) || !readUByte(&ipr)) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (compression != 7) { error(errSyntaxError, getPos(), "Unknown compression type in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } bpc = (Guint *)gmallocn(nComps, sizeof(Guint)); for (i = 0; i < nComps; ++i) { @@ -632,24 +646,24 @@ GBool JPXStream::readBoxes() { if (!haveImgHdr) { error(errSyntaxError, getPos(), "Found bits per component box before image header box in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (dataLen != nComps) { error(errSyntaxError, getPos(), "Invalid bits per component box in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } for (i = 0; i < nComps; ++i) { if (!readUByte(&bpc[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } break; case 0x636F6C72: // color specification cover(11); if (!readColorSpecBox(dataLen)) { - return gFalse; + return jpxDecodeFatalError; } break; case 0x70636c72: // palette @@ -657,15 +671,16 @@ GBool JPXStream::readBoxes() { if (!readUWord(&palette.nEntries) || !readUByte(&palette.nComps)) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } + havePalette = gTrue; palette.bpc = (Guint *)gmallocn(palette.nComps, sizeof(Guint)); palette.c = (int *)gmallocn(palette.nEntries * palette.nComps, sizeof(int)); for (i = 0; i < palette.nComps; ++i) { if (!readUByte(&palette.bpc[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } ++palette.bpc[i]; } @@ -675,14 +690,14 @@ GBool JPXStream::readBoxes() { (palette.bpc[j] & 0x80) ? gTrue : gFalse, &palette.c[i * palette.nComps + j])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } } - havePalette = gTrue; break; case 0x636d6170: // component mapping cover(13); + haveCompMap = gTrue; compMap.nChannels = dataLen / 4; compMap.comp = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint)); compMap.type = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint)); @@ -692,17 +707,17 @@ GBool JPXStream::readBoxes() { !readUByte(&compMap.type[i]) || !readUByte(&compMap.pComp[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } - haveCompMap = gTrue; break; case 0x63646566: // channel definition cover(14); if (!readUWord(&channelDefn.nChannels)) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } + haveChannelDefn = gTrue; channelDefn.idx = (Guint *)gmallocn(channelDefn.nChannels, sizeof(Guint)); channelDefn.type = @@ -714,10 +729,9 @@ GBool JPXStream::readBoxes() { !readUWord(&channelDefn.type[i]) || !readUWord(&channelDefn.assoc[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } - haveChannelDefn = gTrue; break; case 0x6A703263: // contiguous codestream cover(15); @@ -729,28 +743,25 @@ GBool JPXStream::readBoxes() { error(errSyntaxError, getPos(), "JPX stream has no supported color spec"); } - if (!readCodestream(dataLen)) { - return gFalse; + if ((result = readCodestream(dataLen)) != jpxDecodeOk) { + return result; } break; default: cover(16); - for (i = 0; i < dataLen; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; - } + if (bufStr->discardChars(dataLen) != dataLen) { + error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); + return jpxDecodeFatalError; } break; } } - return gTrue; + return jpxDecodeOk; } GBool JPXStream::readColorSpecBox(Guint dataLen) { JPXColorSpec newCS; Guint csApprox, csEnum; - Guint i; GBool ok; ok = gFalse; @@ -852,10 +863,9 @@ GBool JPXStream::readColorSpecBox(Guint dataLen) { case 3: // any ICC profile (JPX) case 4: // vendor color (JPX) cover(18); - for (i = 0; i < dataLen - 3; ++i) { - if (bufStr->getChar() == EOF) { - goto err; - } + if (dataLen > 3 && + bufStr->discardChars(dataLen - 3) != dataLen - 3) { + goto err; } break; } @@ -872,11 +882,11 @@ GBool JPXStream::readColorSpecBox(Guint dataLen) { return gFalse; } -GBool JPXStream::readCodestream(Guint len) { +JPXDecodeResult JPXStream::readCodestream(Guint len) { JPXTile *tile; JPXTileComp *tileComp; int segType; - GBool haveSIZ, haveCOD, haveQCD, haveSOT; + GBool haveSIZ, haveCOD, haveQCD, haveSOT, ok; Guint precinctSize, style; Guint segLen, capabilities, comp, i, j, r; @@ -885,7 +895,7 @@ GBool JPXStream::readCodestream(Guint len) { do { if (!readMarkerHdr(&segType, &segLen)) { error(errSyntaxError, getPos(), "Error in JPX codestream"); - return gFalse; + return jpxDecodeFatalError; } switch (segType) { case 0x4f: // SOC - start of codestream @@ -897,7 +907,7 @@ GBool JPXStream::readCodestream(Guint len) { if (haveSIZ) { error(errSyntaxError, getPos(), "Duplicate SIZ marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (!readUWord(&capabilities) || !readULong(&img.xSize) || @@ -910,12 +920,12 @@ GBool JPXStream::readCodestream(Guint len) { !readULong(&img.yTileOffset) || !readUWord(&img.nComps)) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (haveImgHdr && img.nComps != nComps) { error(errSyntaxError, getPos(), "Different number of components in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.xSize == 0 || img.ySize == 0 || img.xOffset >= img.xSize || img.yOffset >= img.ySize || @@ -925,8 +935,16 @@ GBool JPXStream::readCodestream(Guint len) { img.xTileSize + img.xTileOffset <= img.xOffset || img.yTileSize + img.yTileOffset <= img.yOffset) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; - } + return jpxDecodeFatalError; + } + img.xSizeR = img.xSize >> reduction; + img.ySizeR = img.ySize >> reduction; + img.xOffsetR = img.xOffset >> reduction; + img.yOffsetR = img.yOffset >> reduction; + img.xTileSizeR = img.xTileSize >> reduction; + img.yTileSizeR = img.yTileSize >> reduction; + img.xTileOffsetR = img.xTileOffset >> reduction; + img.yTileOffsetR = img.yTileOffset >> reduction; img.nXTiles = (img.xSize - img.xTileOffset + img.xTileSize - 1) / img.xTileSize; img.nYTiles = (img.ySize - img.yTileOffset + img.yTileSize - 1) @@ -936,12 +954,16 @@ GBool JPXStream::readCodestream(Guint len) { img.nXTiles >= INT_MAX / img.nYTiles) { error(errSyntaxError, getPos(), "Bad tile count in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles = (JPXTile *)gmallocn(img.nXTiles * img.nYTiles, sizeof(JPXTile)); for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { img.tiles[i].init = gFalse; + img.tiles[i].nextTilePart = 0; + img.tiles[i].tileComps = NULL; + } + for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { img.tiles[i].tileComps = (JPXTileComp *)gmallocn(img.nComps, sizeof(JPXTileComp)); for (comp = 0; comp < img.nComps; ++comp) { @@ -956,12 +978,12 @@ GBool JPXStream::readCodestream(Guint len) { !readUByte(&img.tiles[0].tileComps[comp].hSep) || !readUByte(&img.tiles[0].tileComps[comp].vSep)) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.tiles[0].tileComps[comp].hSep == 0 || img.tiles[0].tileComps[comp].vSep == 0) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].sgned = (img.tiles[0].tileComps[comp].prec & 0x80) ? gTrue : gFalse; @@ -978,7 +1000,7 @@ GBool JPXStream::readCodestream(Guint len) { if (!haveSIZ) { error(errSyntaxError, getPos(), "JPX COD marker segment before SIZ segment"); - return gFalse; + return jpxDecodeFatalError; } if (!readUByte(&img.tiles[0].tileComps[0].style) || !readUByte(&img.tiles[0].progOrder) || @@ -990,14 +1012,21 @@ GBool JPXStream::readCodestream(Guint len) { !readUByte(&img.tiles[0].tileComps[0].codeBlockStyle) || !readUByte(&img.tiles[0].tileComps[0].transform)) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.tiles[0].tileComps[0].nDecompLevels > 32 || img.tiles[0].tileComps[0].codeBlockW > 8 || img.tiles[0].tileComps[0].codeBlockH > 8) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } +#if 1 //~ progression orders 2-4 are unimplemented + if (img.tiles[0].progOrder >= 2) { + error(errUnimplemented, -1, + "JPX progression order {0:d} is unimplemented", + img.tiles[0].progOrder); + } +#endif img.tiles[0].tileComps[0].codeBlockW += 2; img.tiles[0].tileComps[0].codeBlockH += 2; for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { @@ -1035,7 +1064,7 @@ GBool JPXStream::readCodestream(Guint len) { cover(91); if (!readUByte(&precinctSize)) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[0].resLevels[r].precinctWidth = precinctSize & 0x0f; @@ -1065,7 +1094,7 @@ GBool JPXStream::readCodestream(Guint len) { if (!haveCOD) { error(errSyntaxError, getPos(), "JPX COC marker segment before COD segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.nComps > 256 && !readUWord(&comp)) || (img.nComps <= 256 && !readUByte(&comp)) || @@ -1077,13 +1106,13 @@ GBool JPXStream::readCodestream(Guint len) { !readUByte(&img.tiles[0].tileComps[comp].codeBlockStyle) || !readUByte(&img.tiles[0].tileComps[comp].transform)) { error(errSyntaxError, getPos(), "Error in JPX COC marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.tiles[0].tileComps[comp].nDecompLevels > 32 || img.tiles[0].tileComps[comp].codeBlockW > 8 || img.tiles[0].tileComps[comp].codeBlockH > 8) { error(errSyntaxError, getPos(), "Error in JPX COC marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].style = (img.tiles[0].tileComps[comp].style & ~1) | (style & 1); @@ -1117,7 +1146,7 @@ GBool JPXStream::readCodestream(Guint len) { if (img.tiles[0].tileComps[comp].style & 0x01) { if (!readUByte(&precinctSize)) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].resLevels[r].precinctWidth = precinctSize & 0x0f; @@ -1142,16 +1171,16 @@ GBool JPXStream::readCodestream(Guint len) { if (!haveSIZ) { error(errSyntaxError, getPos(), "JPX QCD marker segment before SIZ segment"); - return gFalse; + return jpxDecodeFatalError; } if (!readUByte(&img.tiles[0].tileComps[0].quantStyle)) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x00) { if (segLen <= 3) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[0].nQuantSteps = segLen - 3; img.tiles[0].tileComps[0].quantSteps = @@ -1161,7 +1190,7 @@ GBool JPXStream::readCodestream(Guint len) { for (i = 0; i < img.tiles[0].tileComps[0].nQuantSteps; ++i) { if (!readUByte(&img.tiles[0].tileComps[0].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x01) { @@ -1172,12 +1201,12 @@ GBool JPXStream::readCodestream(Guint len) { sizeof(Guint)); if (!readUWord(&img.tiles[0].tileComps[0].quantSteps[0])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } else if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x02) { if (segLen < 5) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[0].nQuantSteps = (segLen - 3) / 2; img.tiles[0].tileComps[0].quantSteps = @@ -1187,12 +1216,12 @@ GBool JPXStream::readCodestream(Guint len) { for (i = 0; i < img.tiles[0].tileComps[0].nQuantSteps; ++i) { if (!readUWord(&img.tiles[0].tileComps[0].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { for (comp = 0; comp < img.nComps; ++comp) { @@ -1219,19 +1248,19 @@ GBool JPXStream::readCodestream(Guint len) { if (!haveQCD) { error(errSyntaxError, getPos(), "JPX QCC marker segment before QCD segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.nComps > 256 && !readUWord(&comp)) || (img.nComps <= 256 && !readUByte(&comp)) || comp >= img.nComps || !readUByte(&img.tiles[0].tileComps[comp].quantStyle)) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x00) { if (segLen <= (img.nComps > 256 ? 5U : 4U)) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].nQuantSteps = segLen - (img.nComps > 256 ? 5 : 4); @@ -1242,7 +1271,7 @@ GBool JPXStream::readCodestream(Guint len) { for (i = 0; i < img.tiles[0].tileComps[comp].nQuantSteps; ++i) { if (!readUByte(&img.tiles[0].tileComps[comp].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x01) { @@ -1253,12 +1282,12 @@ GBool JPXStream::readCodestream(Guint len) { sizeof(Guint)); if (!readUWord(&img.tiles[0].tileComps[comp].quantSteps[0])) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } } else if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x02) { if (segLen < (img.nComps > 256 ? 5U : 4U) + 2) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].nQuantSteps = (segLen - (img.nComps > 256 ? 5 : 4)) / 2; @@ -1269,12 +1298,12 @@ GBool JPXStream::readCodestream(Guint len) { for (i = 0; i < img.tiles[0].tileComps[comp].nQuantSteps; ++i) { if (!readUWord(&img.tiles[0].tileComps[comp].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } for (i = 1; i < img.nXTiles * img.nYTiles; ++i) { img.tiles[i].tileComps[comp].quantStyle = @@ -1295,11 +1324,10 @@ GBool JPXStream::readCodestream(Guint len) { cover(25); #if 1 //~ ROI is unimplemented error(errUnimplemented, -1, "got a JPX RGN segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); + return jpxDecodeFatalError; } #else if ((img.nComps > 256 && !readUWord(&comp)) || @@ -1308,7 +1336,7 @@ GBool JPXStream::readCodestream(Guint len) { !readUByte(&compInfo[comp].defROI.style) || !readUByte(&compInfo[comp].defROI.shift)) { error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); - return gFalse; + return jpxDecodeFatalError; } #endif break; @@ -1316,11 +1344,10 @@ GBool JPXStream::readCodestream(Guint len) { cover(26); #if 1 //~ progression order changes are unimplemented error(errUnimplemented, -1, "got a JPX POC segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); + return jpxDecodeFatalError; } #else nProgs = (segLen - 2) / (img.nComps > 256 ? 9 : 7); @@ -1335,7 +1362,7 @@ GBool JPXStream::readCodestream(Guint len) { !(img.nComps <= 256 && readUByte(&progs[i].endComp)) || !readUByte(&progs[i].progOrder)) { error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); - return gFalse; + return jpxDecodeFatalError; } } #endif @@ -1344,52 +1371,47 @@ GBool JPXStream::readCodestream(Guint len) { cover(27); #if 1 //~ packed packet headers are unimplemented error(errUnimplemented, -1, "Got a JPX PPM segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PPM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PPM marker segment"); + return jpxDecodeFatalError; } #endif break; case 0x55: // TLM - tile-part lengths // skipped cover(28); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX TLM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX TLM marker segment"); + return jpxDecodeFatalError; } break; case 0x57: // PLM - packet length, main header // skipped cover(29); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PLM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PLM marker segment"); + return jpxDecodeFatalError; } break; case 0x63: // CRG - component registration // skipped cover(30); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX CRG marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX CRG marker segment"); + return jpxDecodeFatalError; } break; case 0x64: // COM - comment // skipped cover(31); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); + return jpxDecodeFatalError; } break; case 0x90: // SOT - start of tile @@ -1400,10 +1422,8 @@ GBool JPXStream::readCodestream(Guint len) { cover(33); error(errSyntaxError, getPos(), "Unknown marker segment {0:02x} in JPX stream", segType); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - break; - } + if (segLen > 2) { + bufStr->discardChars(segLen - 2); } break; } @@ -1412,27 +1432,30 @@ GBool JPXStream::readCodestream(Guint len) { if (!haveSIZ) { error(errSyntaxError, getPos(), "Missing SIZ marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (!haveCOD) { error(errSyntaxError, getPos(), "Missing COD marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (!haveQCD) { error(errSyntaxError, getPos(), "Missing QCD marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } //----- read the tile-parts + ok = gTrue; while (1) { if (!readTilePart()) { - return gFalse; + ok = gFalse; + break; } if (!readMarkerHdr(&segType, &segLen)) { error(errSyntaxError, getPos(), "Error in JPX codestream"); - return gFalse; + ok = gFalse; + break; } if (segType != 0x90) { // SOT - start of tile break; @@ -1441,7 +1464,7 @@ GBool JPXStream::readCodestream(Guint len) { if (segType != 0xd9) { // EOC - end of codestream error(errSyntaxError, getPos(), "Missing EOC marker in JPX codestream"); - return gFalse; + ok = gFalse; } //----- finish decoding the image @@ -1449,20 +1472,20 @@ GBool JPXStream::readCodestream(Guint len) { tile = &img.tiles[i]; if (!tile->init) { error(errSyntaxError, getPos(), "Uninitialized tile in JPX codestream"); - return gFalse; + return jpxDecodeFatalError; } for (comp = 0; comp < img.nComps; ++comp) { tileComp = &tile->tileComps[comp]; inverseTransform(tileComp); } if (!inverseMultiCompAndDC(tile)) { - return gFalse; + return jpxDecodeFatalError; } } //~ can free memory below tileComps here, and also tileComp.buf - return gTrue; + return ok ? jpxDecodeOk : jpxDecodeNonFatalError; } GBool JPXStream::readTilePart() { @@ -1490,11 +1513,16 @@ GBool JPXStream::readTilePart() { return gFalse; } - if ((tilePartIdx > 0 && !img.tiles[tileIdx].init) || - tileIdx >= img.nXTiles * img.nYTiles) { - error(errSyntaxError, getPos(), "Weird tile index in JPX stream"); + // check tileIdx and tilePartIdx + // (this ignores nTileParts, because some encoders get it wrong) + if (tileIdx >= img.nXTiles * img.nYTiles || + tilePartIdx != img.tiles[tileIdx].nextTilePart || + (tilePartIdx > 0 && !img.tiles[tileIdx].init) || + (tilePartIdx == 0 && img.tiles[tileIdx].init)) { + error(errSyntaxError, getPos(), "Weird tile-part header in JPX stream"); return gFalse; } + ++img.tiles[tileIdx].nextTilePart; tilePartToEOC = tilePartLen == 0; tilePartLen -= 12; // subtract size of SOT segment @@ -1527,6 +1555,13 @@ GBool JPXStream::readTilePart() { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); return gFalse; } +#if 1 //~ progression orders 2-4 are unimplemented + if (img.tiles[tileIdx].progOrder >= 2) { + error(errUnimplemented, -1, + "JPX progression order {0:d} is unimplemented", + img.tiles[tileIdx].progOrder); + } +#endif img.tiles[tileIdx].tileComps[0].codeBlockW += 2; img.tiles[tileIdx].tileComps[0].codeBlockH += 2; for (comp = 0; comp < img.nComps; ++comp) { @@ -1760,11 +1795,10 @@ GBool JPXStream::readTilePart() { cover(38); #if 1 //~ ROI is unimplemented error(errUnimplemented, -1, "Got a JPX RGN segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); + return gFalse; } #else if ((img.nComps > 256 && !readUWord(&comp)) || @@ -1781,11 +1815,10 @@ GBool JPXStream::readTilePart() { cover(39); #if 1 //~ progression order changes are unimplemented error(errUnimplemented, -1, "Got a JPX POC segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); + return gFalse; } #else nTileProgs = (segLen - 2) / (img.nComps > 256 ? 9 : 7); @@ -1809,31 +1842,28 @@ GBool JPXStream::readTilePart() { cover(40); #if 1 //~ packed packet headers are unimplemented error(errUnimplemented, -1, "Got a JPX PPT segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PPT marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PPT marker segment"); + return gFalse; } #endif case 0x58: // PLT - packet length, tile-part header // skipped cover(41); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PLT marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PLT marker segment"); + return gFalse; } break; case 0x64: // COM - comment // skipped cover(42); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); + return gFalse; } break; case 0x93: // SOD - start of data @@ -1845,10 +1875,8 @@ GBool JPXStream::readTilePart() { error(errSyntaxError, getPos(), "Unknown marker segment {0:02x} in JPX tile-part stream", segType); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - break; - } + if (segLen > 2) { + bufStr->discardChars(segLen - 2); } break; } @@ -1886,12 +1914,13 @@ GBool JPXStream::readTilePart() { tileComp->y0 = jpxCeilDiv(tile->y0, tileComp->vSep); tileComp->x1 = jpxCeilDiv(tile->x1, tileComp->hSep); tileComp->y1 = jpxCeilDiv(tile->y1, tileComp->vSep); - tileComp->w = tileComp->x1 - tileComp->x0; tileComp->cbW = 1 << tileComp->codeBlockW; tileComp->cbH = 1 << tileComp->codeBlockH; - tileComp->data = (int *)gmallocn((tileComp->x1 - tileComp->x0) * - (tileComp->y1 - tileComp->y0), - sizeof(int)); + tileComp->w = (tileComp->x1 - tileComp->x0 + (1 << reduction) - 1) + >> reduction; + tileComp->h = (tileComp->y1 - tileComp->y0 + (1 << reduction) - 1) + >> reduction; + tileComp->data = (int *)gmallocn(tileComp->w * tileComp->h, sizeof(int)); if (tileComp->x1 - tileComp->x0 > tileComp->y1 - tileComp->y0) { n = tileComp->x1 - tileComp->x0; } else { @@ -1927,6 +1956,9 @@ GBool JPXStream::readTilePart() { } resLevel->precincts = (JPXPrecinct *)gmallocn(1, sizeof(JPXPrecinct)); for (pre = 0; pre < 1; ++pre) { + resLevel->precincts[pre].subbands = NULL; + } + for (pre = 0; pre < 1; ++pre) { precinct = &resLevel->precincts[pre]; precinct->x0 = resLevel->x0; precinct->y0 = resLevel->y0; @@ -1936,6 +1968,11 @@ GBool JPXStream::readTilePart() { precinct->subbands = (JPXSubband *)gmallocn(nSBs, sizeof(JPXSubband)); for (sb = 0; sb < nSBs; ++sb) { + precinct->subbands[sb].inclusion = NULL; + precinct->subbands[sb].zeroBitPlane = NULL; + precinct->subbands[sb].cbs = NULL; + } + for (sb = 0; sb < nSBs; ++sb) { subband = &precinct->subbands[sb]; subband->x0 = resLevel->bx0[sb]; subband->y0 = resLevel->by0[sb]; @@ -1973,6 +2010,12 @@ GBool JPXStream::readTilePart() { subband->cbs = (JPXCodeBlock *)gmallocn(subband->nXCBs * subband->nYCBs, sizeof(JPXCodeBlock)); + for (k = 0; k < subband->nXCBs * subband->nYCBs; ++k) { + subband->cbs[k].dataLen = NULL; + subband->cbs[k].touched = NULL; + subband->cbs[k].arithDecoder = NULL; + subband->cbs[k].stats = NULL; + } sbx0 = jpxFloorDivPow2(subband->x0, tileComp->codeBlockW); sby0 = jpxFloorDivPow2(subband->y0, tileComp->codeBlockH); if (r == 0) { // (NL)LL @@ -2013,21 +2056,25 @@ GBool JPXStream::readTilePart() { cb->nZeroBitPlanes = 0; cb->dataLenSize = 1; cb->dataLen = (Guint *)gmalloc(sizeof(Guint)); - cb->coeffs = sbCoeffs - + (cb->y0 - subband->y0) * tileComp->w - + (cb->x0 - subband->x0); - cb->touched = (char *)gmalloc(1 << (tileComp->codeBlockW - + tileComp->codeBlockH)); - cb->len = 0; - for (cbj = 0; cbj < cb->y1 - cb->y0; ++cbj) { - for (cbi = 0; cbi < cb->x1 - cb->x0; ++cbi) { - cb->coeffs[cbj * tileComp->w + cbi] = 0; + if (r <= tileComp->nDecompLevels - reduction) { + cb->coeffs = sbCoeffs + + (cb->y0 - subband->y0) * tileComp->w + + (cb->x0 - subband->x0); + cb->touched = (char *)gmalloc(1 << (tileComp->codeBlockW + + tileComp->codeBlockH)); + cb->len = 0; + for (cbj = 0; cbj < cb->y1 - cb->y0; ++cbj) { + for (cbi = 0; cbi < cb->x1 - cb->x0; ++cbi) { + cb->coeffs[cbj * tileComp->w + cbi] = 0; + } } + memset(cb->touched, 0, + (1 << (tileComp->codeBlockW + tileComp->codeBlockH))); + } else { + cb->coeffs = NULL; + cb->touched = NULL; + cb->len = 0; } - memset(cb->touched, 0, - (1 << (tileComp->codeBlockW + tileComp->codeBlockH))); - cb->arithDecoder = NULL; - cb->stats = NULL; ++cb; } } @@ -2381,7 +2428,21 @@ GBool JPXStream::readCodeBlockData(JPXTileComp *tileComp, Guint horiz, vert, diag, all, cx, xorBit; int horizSign, vertSign, bit; int segSym; - Guint i, x, y0, y1; + Guint n, i, x, y0, y1; + + if (res > tileComp->nDecompLevels - reduction) { + // skip the codeblock data + if (tileComp->codeBlockStyle & 0x04) { + n = 0; + for (i = 0; i < cb->nCodingPasses; ++i) { + n += cb->dataLen[i]; + } + } else { + n = cb->dataLen[0]; + } + bufStr->discardChars(n); + return gTrue; + } if (cb->arithDecoder) { cover(63); @@ -2735,7 +2796,7 @@ void JPXStream::inverseTransform(JPXTileComp *tileComp) { } if (tileComp->transform == 0) { cover(71); - shift += fracBits; + shift += fracBits - tileComp->prec; } // do fixed point adjustment and dequantization on (NL)LL @@ -2766,7 +2827,7 @@ void JPXStream::inverseTransform(JPXTileComp *tileComp) { cover(96); if (tileComp->transform == 0) { cover(97); - val &= -1 << fracBits; + val &= -1 << (fracBits - tileComp->prec); } } else { cover(98); @@ -2782,7 +2843,7 @@ void JPXStream::inverseTransform(JPXTileComp *tileComp) { //----- IDWT for each level - for (r = 1; r <= tileComp->nDecompLevels; ++r) { + for (r = 1; r <= tileComp->nDecompLevels - reduction; ++r) { resLevel = &tileComp->resLevels[r]; // (n)LL is already in the upper-left corner of the @@ -2837,7 +2898,7 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, } if (tileComp->transform == 0) { cover(103); - shift += fracBits; + shift += fracBits - tileComp->prec; } // fixed point adjustment and dequantization @@ -2868,7 +2929,7 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, if (qStyle == 0) { cover(76); if (tileComp->transform == 0) { - val &= -1 << fracBits; + val &= -1 << (fracBits - tileComp->prec); } } else { cover(77); @@ -3103,8 +3164,8 @@ GBool JPXStream::inverseMultiCompAndDC(JPXTile *tile) { if (tile->tileComps[0].transform == 0) { cover(87); j = 0; - for (y = 0; y < tile->tileComps[0].y1 - tile->tileComps[0].y0; ++y) { - for (x = 0; x < tile->tileComps[0].x1 - tile->tileComps[0].x0; ++x) { + for (y = 0; y < tile->tileComps[0].h; ++y) { + for (x = 0; x < tile->tileComps[0].w; ++x) { d0 = tile->tileComps[0].data[j]; d1 = tile->tileComps[1].data[j]; d2 = tile->tileComps[2].data[j]; @@ -3120,8 +3181,8 @@ GBool JPXStream::inverseMultiCompAndDC(JPXTile *tile) { } else { cover(88); j = 0; - for (y = 0; y < tile->tileComps[0].y1 - tile->tileComps[0].y0; ++y) { - for (x = 0; x < tile->tileComps[0].x1 - tile->tileComps[0].x0; ++x) { + for (y = 0; y < tile->tileComps[0].h; ++y) { + for (x = 0; x < tile->tileComps[0].w; ++x) { d0 = tile->tileComps[0].data[j]; d1 = tile->tileComps[1].data[j]; d2 = tile->tileComps[2].data[j]; @@ -3144,12 +3205,12 @@ GBool JPXStream::inverseMultiCompAndDC(JPXTile *tile) { minVal = -(1 << (tileComp->prec - 1)); maxVal = (1 << (tileComp->prec - 1)) - 1; dataPtr = tileComp->data; - for (y = 0; y < tileComp->y1 - tileComp->y0; ++y) { - for (x = 0; x < tileComp->x1 - tileComp->x0; ++x) { + for (y = 0; y < tileComp->h; ++y) { + for (x = 0; x < tileComp->w; ++x) { coeff = *dataPtr; if (tileComp->transform == 0) { cover(109); - coeff >>= fracBits; + coeff >>= fracBits - tileComp->prec; } if (coeff < minVal) { cover(110); @@ -3168,12 +3229,12 @@ GBool JPXStream::inverseMultiCompAndDC(JPXTile *tile) { maxVal = (1 << tileComp->prec) - 1; zeroVal = 1 << (tileComp->prec - 1); dataPtr = tileComp->data; - for (y = 0; y < tileComp->y1 - tileComp->y0; ++y) { - for (x = 0; x < tileComp->x1 - tileComp->x0; ++x) { + for (y = 0; y < tileComp->h; ++y) { + for (x = 0; x < tileComp->w; ++x) { coeff = *dataPtr; if (tileComp->transform == 0) { cover(112); - coeff >>= fracBits; + coeff >>= fracBits - tileComp->prec; } coeff += zeroVal; if (coeff < 0) { @@ -3339,16 +3400,12 @@ GBool JPXStream::readBits(int nBits, Guint *x) { } void JPXStream::skipSOP() { - int i; - // SOP occurs at the start of the packet header, so we don't need to // worry about bit-stuff prior to it if (byteCount >= 6 && bufStr->lookChar(0) == 0xff && bufStr->lookChar(1) == 0x91) { - for (i = 0; i < 6; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(6); byteCount -= 6; bitBufLen = 0; bitBufSkip = gFalse; @@ -3356,15 +3413,13 @@ void JPXStream::skipSOP() { } void JPXStream::skipEPH() { - int i, k; + int k; k = bitBufSkip ? 1 : 0; if (byteCount >= (Guint)(k + 2) && bufStr->lookChar(k) == 0xff && bufStr->lookChar(k + 1) == 0x92) { - for (i = 0; i < k + 2; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(k + 2); byteCount -= k + 2; bitBufLen = 0; bitBufSkip = gFalse; diff --git a/xpdf/JPXStream.h b/xpdf/JPXStream.h index 2c46ca0..d00a55e 100644 --- a/xpdf/JPXStream.h +++ b/xpdf/JPXStream.h @@ -199,7 +199,7 @@ struct JPXTileComp { //----- computed Guint x0, y0, x1, y1; // bounds of the tile-comp, in ref coords - Guint w; // x1 - x0 + Guint w, h; // data size = {x1 - x0, y1 - y0} >> reduction Guint cbW; // code-block width Guint cbH; // code-block height @@ -234,6 +234,9 @@ struct JPXTile { Guint precinct; // precinct Guint layer; // layer + //----- tile part info + Guint nextTilePart; // next expected tile-part + //----- children JPXTileComp *tileComps; // the tile-components (len = JPXImage.nComps) }; @@ -247,6 +250,11 @@ struct JPXImage { Guint xTileSize, yTileSize; // size of tiles Guint xTileOffset, // offset of first tile yTileOffset; + Guint xSizeR, ySizeR; // size of reference grid >> reduction + Guint xOffsetR, yOffsetR; // image offset >> reduction + Guint xTileSizeR, yTileSizeR; // size of tiles >> reduction + Guint xTileOffsetR, // offset of first tile >> reduction + yTileOffsetR; Guint nComps; // number of components //----- computed @@ -259,6 +267,14 @@ struct JPXImage { //------------------------------------------------------------------------ +enum JPXDecodeResult { + jpxDecodeOk, + jpxDecodeNonFatalError, + jpxDecodeFatalError +}; + +//------------------------------------------------------------------------ + class JPXStream: public FilterStream { public: @@ -273,14 +289,15 @@ public: virtual GBool isBinary(GBool last = gTrue); virtual void getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode); + void reduceResolution(int reductionA) { reduction = reductionA; } private: void fillReadBuf(); void getImageParams2(int *bitsPerComponent, StreamColorSpaceMode *csMode); - GBool readBoxes(); + JPXDecodeResult readBoxes(); GBool readColorSpecBox(Guint dataLen); - GBool readCodestream(Guint len); + JPXDecodeResult readCodestream(Guint len); GBool readTilePart(); GBool readTilePartData(Guint tileIdx, Guint tilePartLen, GBool tilePartToEOC); @@ -314,6 +331,7 @@ private: Guint nComps; // number of components Guint *bpc; // bits per component, for each component Guint width, height; // image size + int reduction; // log2(reduction in resolution) GBool haveImgHdr; // set if a JP2/JPX image header has been // found JPXColorSpec cs; // color specification diff --git a/xpdf/Lexer.h b/xpdf/Lexer.h index f6ad9ce..3f46689 100644 --- a/xpdf/Lexer.h +++ b/xpdf/Lexer.h @@ -53,13 +53,12 @@ public: Stream *getStream() { return curStr.isNone() ? (Stream *)NULL : curStr.getStream(); } - // Get current position in file. This is only used for error - // messages, so it returns an int instead of a Guint. - int getPos() - { return curStr.isNone() ? -1 : (int)curStr.streamGetPos(); } + // Get current position in file. + GFileOffset getPos() + { return curStr.isNone() ? -1 : curStr.streamGetPos(); } // Set position in file. - void setPos(Guint pos, int dir = 0) + void setPos(GFileOffset pos, int dir = 0) { if (!curStr.isNone()) curStr.streamSetPos(pos, dir); } // Returns true if <c> is a whitespace character. diff --git a/xpdf/Link.cc b/xpdf/Link.cc index 56a2963..86a3c41 100644 --- a/xpdf/Link.cc +++ b/xpdf/Link.cc @@ -39,7 +39,7 @@ LinkAction *LinkAction::parseDest(Object *obj) { LinkAction *LinkAction::parseAction(Object *obj, GString *baseURI) { LinkAction *action; - Object obj2, obj3, obj4; + Object obj2, obj3, obj4, obj5; if (!obj->isDict()) { error(errSyntaxWarning, -1, "Bad annotation action"); @@ -86,6 +86,30 @@ LinkAction *LinkAction::parseAction(Object *obj, GString *baseURI) { obj3.free(); obj4.free(); + // JavaScript action + } else if (obj2.isName("JavaScript")) { + obj->dictLookup("JS", &obj3); + action = new LinkJavaScript(&obj3); + obj3.free(); + + // SubmitForm action + } else if (obj2.isName("SubmitForm")) { + obj->dictLookup("F", &obj3); + obj->dictLookup("Fields", &obj4); + obj->dictLookup("Flags", &obj5); + action = new LinkSubmitForm(&obj3, &obj4, &obj5); + obj3.free(); + obj4.free(); + obj5.free(); + + // Hide action + } else if (obj2.isName("Hide")) { + obj->dictLookupNF("T", &obj3); + obj->dictLookup("H", &obj4); + action = new LinkHide(&obj3, &obj4); + obj3.free(); + obj4.free(); + // unknown action } else if (obj2.isName()) { action = new LinkUnknown(obj2.getName()); @@ -117,7 +141,7 @@ GString *LinkAction::getFileSpecName(Object *fileSpecObj) { // dictionary } else if (fileSpecObj->isDict()) { -#ifdef WIN32 +#ifdef _WIN32 if (!fileSpecObj->dictLookup("DOS", &obj1)->isString()) { #else if (!fileSpecObj->dictLookup("Unix", &obj1)->isString()) { @@ -139,7 +163,7 @@ GString *LinkAction::getFileSpecName(Object *fileSpecObj) { // system-dependent path manipulation if (name) { -#ifdef WIN32 +#ifdef _WIN32 int i, j; // "//...." --> "\...." @@ -517,7 +541,7 @@ LinkLaunch::LinkLaunch(Object *actionObj) { fileName = getFileSpecName(&obj1); } else { obj1.free(); -#ifdef WIN32 +#ifdef _WIN32 if (actionObj->dictLookup("Win", &obj1)->isDict()) { obj1.dictLookup("F", &obj2); fileName = getFileSpecName(&obj2); @@ -644,6 +668,98 @@ LinkMovie::~LinkMovie() { } //------------------------------------------------------------------------ +// LinkJavaScript +//------------------------------------------------------------------------ + +LinkJavaScript::LinkJavaScript(Object *jsObj) { + char buf[4096]; + int n; + + if (jsObj->isString()) { + js = jsObj->getString()->copy(); + } else if (jsObj->isStream()) { + js = new GString(); + jsObj->streamReset(); + while ((n = jsObj->getStream()->getBlock(buf, sizeof(buf))) > 0) { + js->append(buf, n); + } + jsObj->streamClose(); + } else { + error(errSyntaxError, -1, "JavaScript action JS key is wrong type"); + js = NULL; + } +} + +LinkJavaScript::~LinkJavaScript() { + if (js) { + delete js; + } +} + +//------------------------------------------------------------------------ +// LinkSubmitForm +//------------------------------------------------------------------------ + +LinkSubmitForm::LinkSubmitForm(Object *urlObj, Object *fieldsObj, + Object *flagsObj) { + if (urlObj->isString()) { + url = urlObj->getString()->copy(); + } else { + error(errSyntaxError, -1, "SubmitForm action URL is wrong type"); + url = NULL; + } + + if (fieldsObj->isArray()) { + fieldsObj->copy(&fields); + } else { + if (!fieldsObj->isNull()) { + error(errSyntaxError, -1, "SubmitForm action Fields value is wrong type"); + } + fields.initNull(); + } + + if (flagsObj->isInt()) { + flags = flagsObj->getInt(); + } else { + if (!flagsObj->isNull()) { + error(errSyntaxError, -1, "SubmitForm action Flags value is wrong type"); + } + flags = 0; + } +} + +LinkSubmitForm::~LinkSubmitForm() { + if (url) { + delete url; + } + fields.free(); +} + +//------------------------------------------------------------------------ +// LinkHide +//------------------------------------------------------------------------ + +LinkHide::LinkHide(Object *fieldsObj, Object *hideFlagObj) { + if (fieldsObj->isRef() || fieldsObj->isString() || fieldsObj->isArray()) { + fieldsObj->copy(&fields); + } else { + error(errSyntaxError, -1, "Hide action T value is wrong type"); + fields.initNull(); + } + + if (hideFlagObj->isBool()) { + hideFlag = hideFlagObj->getBool(); + } else { + error(errSyntaxError, -1, "Hide action H value is wrong type"); + hideFlag = gFalse; + } +} + +LinkHide::~LinkHide() { + fields.free(); +} + +//------------------------------------------------------------------------ // LinkUnknown //------------------------------------------------------------------------ @@ -745,7 +861,7 @@ Link::~Link() { Links::Links(Object *annots, GString *baseURI) { Link *link; - Object obj1, obj2; + Object obj1, obj2, obj3; int size; int i; @@ -756,7 +872,10 @@ Links::Links(Object *annots, GString *baseURI) { if (annots->isArray()) { for (i = 0; i < annots->arrayGetLength(); ++i) { if (annots->arrayGet(i, &obj1)->isDict()) { - if (obj1.dictLookup("Subtype", &obj2)->isName("Link")) { + obj1.dictLookup("Subtype", &obj2); + obj1.dictLookup("FT", &obj3); + if (obj2.isName("Link") || + (obj2.isName("Widget") && (obj3.isName("Btn") || obj3.isNull()))) { link = new Link(obj1.getDict(), baseURI); if (link->isOk()) { if (numLinks >= size) { @@ -768,6 +887,7 @@ Links::Links(Object *annots, GString *baseURI) { delete link; } } + obj3.free(); obj2.free(); } obj1.free(); diff --git a/xpdf/Link.h b/xpdf/Link.h index 55fbfa1..480496a 100644 --- a/xpdf/Link.h +++ b/xpdf/Link.h @@ -32,6 +32,9 @@ enum LinkActionKind { actionURI, // URI actionNamed, // named action actionMovie, // movie action + actionJavaScript, // run JavaScript + actionSubmitForm, // submit form + actionHide, // hide annotation actionUnknown // anything else }; @@ -244,8 +247,10 @@ public: virtual ~LinkNamed(); + // Was the LinkNamed created successfully? virtual GBool isOk() { return name != NULL; } + // Accessors. virtual LinkActionKind getKind() { return actionNamed; } GString *getName() { return name; } @@ -265,8 +270,10 @@ public: virtual ~LinkMovie(); + // Was the LinkMovie created successfully? virtual GBool isOk() { return annotRef.num >= 0 || title != NULL; } + // Accessors. virtual LinkActionKind getKind() { return actionMovie; } GBool hasAnnotRef() { return annotRef.num >= 0; } Ref *getAnnotRef() { return &annotRef; } @@ -279,6 +286,81 @@ private: }; //------------------------------------------------------------------------ +// LinkJavaScript +//------------------------------------------------------------------------ + +class LinkJavaScript: public LinkAction { +public: + + LinkJavaScript(Object *jsObj); + + virtual ~LinkJavaScript(); + + // Was the LinkJavaScript created successfully? + virtual GBool isOk() { return js != NULL; } + + // Accessors. + virtual LinkActionKind getKind() { return actionJavaScript; } + GString *getJS() { return js; } + +private: + + GString *js; +}; + +//------------------------------------------------------------------------ +// LinkSubmitForm +//------------------------------------------------------------------------ + +class LinkSubmitForm: public LinkAction { +public: + + LinkSubmitForm(Object *urlObj, Object *fieldsObj, Object *flagsObj); + + virtual ~LinkSubmitForm(); + + // Was the LinkSubmitForm created successfully? + virtual GBool isOk() { return url != NULL; } + + // Accessors. + virtual LinkActionKind getKind() { return actionSubmitForm; } + GString *getURL() { return url; } + Object *getFields() { return &fields; } + int getFlags() { return flags; } + +private: + + GString *url; + Object fields; + int flags; +}; + +//------------------------------------------------------------------------ +// LinkHide +//------------------------------------------------------------------------ + +class LinkHide: public LinkAction { +public: + + LinkHide(Object *fieldsObj, Object *hideFlagObj); + + virtual ~LinkHide(); + + // Was the LinkHide created successfully? + virtual GBool isOk() { return !fields.isNull(); } + + // Accessors. + virtual LinkActionKind getKind() { return actionHide; } + Object *getFields() { return &fields; } + GBool getHideFlag() { return hideFlag; } + +private: + + Object fields; + GBool hideFlag; +}; + +//------------------------------------------------------------------------ // LinkUnknown //------------------------------------------------------------------------ diff --git a/xpdf/Makefile.in b/xpdf/Makefile.in index 5b48eff..de3e676 100644 --- a/xpdf/Makefile.in +++ b/xpdf/Makefile.in @@ -19,18 +19,19 @@ FOFILIBDIR = ../fofi SPLASHSRCDIR = $(srcdir)/../splash SPLASHLIBDIR = ../splash -CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(GOOSRCDIR) -I$(FOFISRCDIR) -I$(SPLASHSRCDIR) -I$(srcdir) @freetype2_CFLAGS@ @Sgm_CFLAGS@ @Xm_CFLAGS@ @Xt_CFLAGS@ @Xp_CFLAGS@ @Xext_CFLAGS@ @Xpm_CFLAGS@ @t1_CFLAGS@ @libpaper_CFLAGS@ @X_CFLAGS@ +CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(srcdir)/.. -I$(GOOSRCDIR) -I$(FOFISRCDIR) -I$(SPLASHSRCDIR) -I$(srcdir) @freetype2_CFLAGS@ @Sgm_CFLAGS@ @Xm_CFLAGS@ @Xt_CFLAGS@ @Xp_CFLAGS@ @Xext_CFLAGS@ @Xpm_CFLAGS@ @libpng_CFLAGS@ @libpaper_CFLAGS@ @X_CFLAGS@ @EXTRA_CFLAGS@ LDFLAGS = @LDFLAGS@ -T1LIBS = @t1_LIBS@ -FTLIBS = @freetype2_LIBS@ +FTLIBS = @freetype2_LIBS@ -lz XLIBS = @Sgm_LIBS@ @Xm_LIBS@ @Xt_LIBS@ @Xp_LIBS@ @Xext_LIBS@ @Xpm_LIBS@ @X_PRE_LIBS@ @X_LIBS@ -lX11 @X_EXTRA_LIBS@ +PNGLIBS = @libpng_LIBS@ + SPLASHLIBS = -L$(SPLASHLIBDIR) -lsplash -OTHERLIBS = @LIBS@ @libpaper_LIBS@ \ +OTHERLIBS = @LIBS@ @libpaper_LIBS@ @EXTRA_LIBS@ \ -L$(FOFILIBDIR) -lfofi \ -L$(GOOLIBDIR) -lGoo @@ -49,6 +50,7 @@ EXE = @EXE@ #------------------------------------------------------------------------ CXX_SRC = \ + $(srcdir)/AcroForm.cc \ $(srcdir)/Annot.cc \ $(srcdir)/Array.cc \ $(srcdir)/BuiltinFont.cc \ @@ -61,11 +63,13 @@ CXX_SRC = \ $(srcdir)/Dict.cc \ $(srcdir)/Error.cc \ $(srcdir)/FontEncodingTables.cc \ + $(srcdir)/Form.cc \ $(srcdir)/Function.cc \ $(srcdir)/Gfx.cc \ $(srcdir)/GfxFont.cc \ $(srcdir)/GfxState.cc \ $(srcdir)/GlobalParams.cc \ + $(srcdir)/HTMLGen.cc \ $(srcdir)/ImageOutputDev.cc \ $(srcdir)/JArithmeticDecoder.cc \ $(srcdir)/JBIG2Stream.cc \ @@ -89,44 +93,94 @@ CXX_SRC = \ $(srcdir)/SplashOutputDev.cc \ $(srcdir)/Stream.cc \ $(srcdir)/TextOutputDev.cc \ + $(srcdir)/TextString.cc \ $(srcdir)/UnicodeMap.cc \ $(srcdir)/UnicodeTypeTable.cc \ + $(srcdir)/XFAForm.cc \ $(srcdir)/XPDFApp.cc \ $(srcdir)/XPDFCore.cc \ $(srcdir)/XPDFTree.cc \ $(srcdir)/XPDFViewer.cc \ $(srcdir)/XpdfPluginAPI.cc \ $(srcdir)/XRef.cc \ + $(srcdir)/Zoox.cc \ $(srcdir)/pdftops.cc \ $(srcdir)/pdftotext.cc \ + $(srcdir)/pdftohtml.cc \ $(srcdir)/pdfinfo.cc \ $(srcdir)/pdffonts.cc \ $(srcdir)/pdfdetach.cc \ $(srcdir)/pdftoppm.cc \ + $(srcdir)/pdftopng.cc \ $(srcdir)/pdfimages.cc \ $(srcdir)/xpdf.cc #------------------------------------------------------------------------ -all: xpdf$(EXE) pdftops$(EXE) pdftotext$(EXE) pdfinfo$(EXE) \ - pdffonts$(EXE) pdfdetach$(EXE) pdftoppm$(EXE) pdfimages$(EXE) +all: xpdf$(EXE) pdftops$(EXE) pdftotext$(EXE) pdftohtml$(EXE) \ + pdfinfo$(EXE) pdffonts$(EXE) pdfdetach$(EXE) pdftoppm$(EXE) \ + pdftopng$(EXE) pdfimages$(EXE) -all-no-x: pdftops$(EXE) pdftotext$(EXE) pdfinfo$(EXE) pdffonts$(EXE) \ - pdfdetach$(EXE) pdfimages$(EXE) +all-no-x: pdftops$(EXE) pdftotext$(EXE) pdftohtml$(EXE) pdfinfo$(EXE) \ + pdffonts$(EXE) pdfdetach$(EXE) pdfimages$(EXE) #------------------------------------------------------------------------ -XPDF_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o Catalog.o \ - CharCodeToUnicode.o CMap.o CoreOutputDev.o Decrypt.o Dict.o \ - Error.o FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFCore.o \ - PDFDoc.o PDFDocEncoding.o PreScanOutputDev.o PSOutputDev.o \ - PSTokenizer.o SecurityHandler.o SplashOutputDev.o Stream.o \ - TextOutputDev.o UnicodeMap.o UnicodeTypeTable.o XPDFApp.o \ - XPDFCore.o XPDFTree.o XPDFViewer.o XpdfPluginAPI.o XRef.o xpdf.o -XPDF_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(T1LIBS) $(FTLIBS) \ +XPDF_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + CoreOutputDev.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFCore.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PreScanOutputDev.o \ + PSOutputDev.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XPDFApp.o \ + XPDFCore.o \ + XPDFTree.o \ + XPDFViewer.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + xpdf.o +XPDF_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ $(XLIBS) $(OTHERLIBS) -lm xpdf$(EXE): $(XPDF_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -134,16 +188,53 @@ xpdf$(EXE): $(XPDF_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFTOPS_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o OptionalContent.o \ - Outline.o Object.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PreScanOutputDev.o PSOutputDev.o PSTokenizer.o \ - SecurityHandler.o SplashOutputDev.o Stream.o UnicodeMap.o \ - XpdfPluginAPI.o XRef.o pdftops.o -PDFTOPS_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(T1LIBS) $(FTLIBS) \ +PDFTOPS_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + OptionalContent.o \ + Outline.o \ + Object.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PreScanOutputDev.o \ + PSOutputDev.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftops.o +PDFTOPS_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ $(OTHERLIBS) -lm pdftops$(EXE): $(PDFTOPS_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -152,15 +243,51 @@ pdftops$(EXE): $(PDFTOPS_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFTOTEXT_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - TextOutputDev.o UnicodeMap.o UnicodeTypeTable.o XpdfPluginAPI.o \ - XRef.o pdftotext.o +PDFTOTEXT_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftotext.o PDFTOTEXT_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdftotext$(EXE): $(PDFTOTEXT_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -169,14 +296,105 @@ pdftotext$(EXE): $(PDFTOTEXT_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFINFO_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdfinfo.o +PDFTOHTML_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + HTMLGen.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftohtml.o +PDFTOHTML_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ + $(OTHERLIBS) $(PNGLIBS) -lm + +pdftohtml$(EXE): $(PDFTOHTML_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o pdftohtml$(EXE) $(PDFTOHTML_OBJS) \ + $(PDFTOHTML_LIBS) + +#------------------------------------------------------------------------ + +PDFINFO_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdfinfo.o PDFINFO_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdfinfo$(EXE): $(PDFINFO_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -185,14 +403,49 @@ pdfinfo$(EXE): $(PDFINFO_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFFONTS_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdffonts.o +PDFFONTS_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdffonts.o PDFFONTS_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdffonts$(EXE): $(PDFFONTS_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -201,14 +454,49 @@ pdffonts$(EXE): $(PDFFONTS_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFDETACH_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdfdetach.o +PDFDETACH_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdfdetach.o PDFDETACH_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdfdetach$(EXE): $(PDFDETACH_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -217,16 +505,53 @@ pdfdetach$(EXE): $(PDFDETACH_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFTOPPM_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o GfxState.o \ - GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o JPXStream.o \ - Lexer.o Link.o NameToCharCode.o Object.o OptionalContent.o \ - Outline.o OutputDev.o Page.o Parser.o PDFDoc.o PDFDocEncoding.o \ - PSTokenizer.o SecurityHandler.o SplashOutputDev.o Stream.o \ - TextOutputDev.o UnicodeMap.o UnicodeTypeTable.o XpdfPluginAPI.o \ - XRef.o pdftoppm.o -PDFTOPPM_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(T1LIBS) $(FTLIBS) \ +PDFTOPPM_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftoppm.o +PDFTOPPM_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ $(OTHERLIBS) -lm pdftoppm$(EXE): $(PDFTOPPM_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -235,14 +560,105 @@ pdftoppm$(EXE): $(PDFTOPPM_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a #------------------------------------------------------------------------ -PDFIMAGES_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o GfxState.o \ - GlobalParams.o ImageOutputDev.o JArithmeticDecoder.o \ - JBIG2Stream.o JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdfimages.o +PDFTOPNG_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftopng.o +PDFTOPNG_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ + $(OTHERLIBS) $(PNGLIBS) -lm + +pdftopng$(EXE): $(PDFTOPNG_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o pdftopng$(EXE) $(PDFTOPNG_OBJS) \ + $(PDFTOPNG_LIBS) + +#------------------------------------------------------------------------ + +PDFIMAGES_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + ImageOutputDev.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdfimages.o PDFIMAGES_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdfimages$(EXE): $(PDFIMAGES_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -255,10 +671,12 @@ clean: rm -f $(XPDF_OBJS) xpdf$(EXE) rm -f $(PDFTOPS_OBJS) pdftops$(EXE) rm -f $(PDFTOTEXT_OBJS) pdftotext$(EXE) + rm -f $(PDFTOHTML_OBJS) pdftohtml$(EXE) rm -f $(PDFINFO_OBJS) pdfinfo$(EXE) rm -f $(PDFFONTS_OBJS) pdffonts$(EXE) rm -f $(PDFDETACH_OBJS) pdfdetach$(EXE) rm -f $(PDFTOPPM_OBJS) pdftoppm$(EXE) + rm -f $(PDFTOPNG_OBJS) pdftopng$(EXE) rm -f $(PDFIMAGES_OBJS) pdfimages$(EXE) #------------------------------------------------------------------------ @@ -266,4 +684,4 @@ clean: depend: $(CXX) $(CXXFLAGS) -MM $(CXX_SRC) >Makefile.dep -include Makefile.dep +-include Makefile.dep diff --git a/xpdf/Object.h b/xpdf/Object.h index e1abed9..64c75f7 100644 --- a/xpdf/Object.h +++ b/xpdf/Object.h @@ -19,6 +19,7 @@ #include <string.h> #include "gtypes.h" #include "gmem.h" +#include "gfile.h" #include "GString.h" class XRef; @@ -179,9 +180,10 @@ public: void streamClose(); int streamGetChar(); int streamLookChar(); + int streamGetBlock(char *blk, int size); char *streamGetLine(char *buf, int size); - Guint streamGetPos(); - void streamSetPos(Guint pos, int dir = 0); + GFileOffset streamGetPos(); + void streamSetPos(GFileOffset pos, int dir = 0); Dict *streamGetDict(); // Output. @@ -288,13 +290,16 @@ inline int Object::streamGetChar() inline int Object::streamLookChar() { return stream->lookChar(); } +inline int Object::streamGetBlock(char *blk, int size) + { return stream->getBlock(blk, size); } + inline char *Object::streamGetLine(char *buf, int size) { return stream->getLine(buf, size); } -inline Guint Object::streamGetPos() +inline GFileOffset Object::streamGetPos() { return stream->getPos(); } -inline void Object::streamSetPos(Guint pos, int dir) +inline void Object::streamSetPos(GFileOffset pos, int dir) { stream->setPos(pos, dir); } inline Dict *Object::streamGetDict() diff --git a/xpdf/OptionalContent.cc b/xpdf/OptionalContent.cc index 589719c..bebb674 100644 --- a/xpdf/OptionalContent.cc +++ b/xpdf/OptionalContent.cc @@ -2,7 +2,7 @@ // // OptionalContent.cc // -// Copyright 2008 Glyph & Cog, LLC +// Copyright 2008-2013 Glyph & Cog, LLC // //======================================================================== @@ -17,7 +17,7 @@ #include "Error.h" #include "Object.h" #include "PDFDoc.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "OptionalContent.h" //------------------------------------------------------------------------ @@ -150,70 +150,79 @@ GBool OptionalContent::evalOCObject(Object *obj, GBool *visible) { } } obj->fetch(xref, &obj2); - if (obj2.isDict("OCMD")) { - if (obj2.dictLookup("VE", &obj3)->isArray()) { - *visible = evalOCVisibilityExpr(&obj3, 0); - obj3.free(); - } else { - obj3.free(); - policy = ocPolicyAnyOn; - if (obj2.dictLookup("P", &obj3)->isName()) { - if (obj3.isName("AllOn")) { - policy = ocPolicyAllOn; - } else if (obj3.isName("AnyOn")) { - policy = ocPolicyAnyOn; - } else if (obj3.isName("AnyOff")) { - policy = ocPolicyAnyOff; - } else if (obj3.isName("AllOff")) { - policy = ocPolicyAllOff; - } + if (!obj2.isDict("OCMD")) { + obj2.free(); + return gFalse; + } + if (obj2.dictLookup("VE", &obj3)->isArray()) { + *visible = evalOCVisibilityExpr(&obj3, 0); + obj3.free(); + } else { + obj3.free(); + policy = ocPolicyAnyOn; + if (obj2.dictLookup("P", &obj3)->isName()) { + if (obj3.isName("AllOn")) { + policy = ocPolicyAllOn; + } else if (obj3.isName("AnyOn")) { + policy = ocPolicyAnyOn; + } else if (obj3.isName("AnyOff")) { + policy = ocPolicyAnyOff; + } else if (obj3.isName("AllOff")) { + policy = ocPolicyAllOff; } - obj3.free(); - obj2.dictLookupNF("OCGs", &obj3); - ocg = NULL; - if (obj3.isRef()) { - ref = obj3.getRef(); - ocg = findOCG(&ref); + } + obj3.free(); + obj2.dictLookupNF("OCGs", &obj3); + ocg = NULL; + if (obj3.isRef()) { + ref = obj3.getRef(); + ocg = findOCG(&ref); + } + if (ocg) { + *visible = (policy == ocPolicyAllOn || policy == ocPolicyAnyOn) ? + ocg->getState() : !ocg->getState(); + } else { + *visible = policy == ocPolicyAllOn || policy == ocPolicyAllOff; + if (!obj3.fetch(xref, &obj4)->isArray()) { + obj4.free(); + obj3.free(); + obj2.free(); + return gFalse; } - if (ocg) { - *visible = (policy == ocPolicyAllOn || policy == ocPolicyAnyOn) ? - ocg->getState() : !ocg->getState(); - } else { - *visible = gTrue; - if (obj3.fetch(xref, &obj4)->isArray()) { - for (i = 0; i < obj4.arrayGetLength(); ++i) { - obj4.arrayGetNF(i, &obj5); - if (obj5.isRef()) { - ref = obj5.getRef(); - if ((ocg = findOCG(&ref))) { - switch (policy) { - case ocPolicyAllOn: - *visible = *visible && ocg->getState(); - break; - case ocPolicyAnyOn: - *visible = *visible || ocg->getState(); - break; - case ocPolicyAnyOff: - *visible = *visible || !ocg->getState(); - break; - case ocPolicyAllOff: - *visible = *visible && !ocg->getState(); - break; - } - } - } + for (i = 0; i < obj4.arrayGetLength(); ++i) { + obj4.arrayGetNF(i, &obj5); + if (obj5.isRef()) { + ref = obj5.getRef(); + if (!(ocg = findOCG(&ref))) { obj5.free(); + obj4.free(); + obj3.free(); + obj2.free(); + return gFalse; + } + switch (policy) { + case ocPolicyAllOn: + *visible = *visible && ocg->getState(); + break; + case ocPolicyAnyOn: + *visible = *visible || ocg->getState(); + break; + case ocPolicyAnyOff: + *visible = *visible || !ocg->getState(); + break; + case ocPolicyAllOff: + *visible = *visible && !ocg->getState(); + break; } } - obj4.free(); + obj5.free(); } - obj3.free(); + obj4.free(); } - obj2.free(); - return gTrue; + obj3.free(); } obj2.free(); - return gFalse; + return gTrue; } GBool OptionalContent::evalOCVisibilityExpr(Object *expr, int recursion) { @@ -279,12 +288,9 @@ GBool OptionalContent::evalOCVisibilityExpr(Object *expr, int recursion) { //------------------------------------------------------------------------ OptionalContentGroup *OptionalContentGroup::parse(Ref *refA, Object *obj) { - Unicode *nameA; - int nameLenA; + TextString *nameA; Object obj1, obj2, obj3; - GString *s; OCUsageState viewStateA, printStateA; - int i; if (!obj->isDict()) { return NULL; @@ -294,22 +300,7 @@ OptionalContentGroup *OptionalContentGroup::parse(Ref *refA, Object *obj) { obj1.free(); return NULL; } - s = obj1.getString(); - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - nameLenA = (s->getLength() - 2) / 2; - nameA = (Unicode *)gmallocn(nameLenA, sizeof(Unicode)); - for (i = 0; i < nameLenA; ++i) { - nameA[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } - } else { - nameLenA = s->getLength(); - nameA = (Unicode *)gmallocn(nameLenA, sizeof(Unicode)); - for (i = 0; i < nameLenA; ++i) { - nameA[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } - } + nameA = new TextString(obj1.getString()); obj1.free(); viewStateA = printStateA = ocUsageUnset; @@ -339,30 +330,35 @@ OptionalContentGroup *OptionalContentGroup::parse(Ref *refA, Object *obj) { } obj1.free(); - return new OptionalContentGroup(refA, nameA, nameLenA, - viewStateA, printStateA); + return new OptionalContentGroup(refA, nameA, viewStateA, printStateA); } -OptionalContentGroup::OptionalContentGroup(Ref *refA, Unicode *nameA, - int nameLenA, +OptionalContentGroup::OptionalContentGroup(Ref *refA, TextString *nameA, OCUsageState viewStateA, OCUsageState printStateA) { ref = *refA; name = nameA; - nameLen = nameLenA; viewState = viewStateA; printState = printStateA; state = gTrue; } OptionalContentGroup::~OptionalContentGroup() { - gfree(name); + delete name; } GBool OptionalContentGroup::matches(Ref *refA) { return refA->num == ref.num && refA->gen == ref.gen; } +Unicode *OptionalContentGroup::getName() { + return name->getUnicode(); +} + +int OptionalContentGroup::getNameLength() { + return name->getLength(); +} + //------------------------------------------------------------------------ OCDisplayNode *OCDisplayNode::parse(Object *obj, OptionalContent *oc, @@ -404,8 +400,10 @@ OCDisplayNode *OCDisplayNode::parse(Object *obj, OptionalContent *oc, obj2.arrayGetNF(i, &obj3); if ((child = OCDisplayNode::parse(&obj3, oc, xref, recursion + 1))) { if (!child->ocg && !child->name && node->getNumChildren() > 0) { - node->getChild(node->getNumChildren() - 1)-> - addChildren(child->takeChildren()); + if (child->getNumChildren() > 0) { + node->getChild(node->getNumChildren() - 1)-> + addChildren(child->takeChildren()); + } delete child; } else { node->addChild(child); @@ -418,42 +416,19 @@ OCDisplayNode *OCDisplayNode::parse(Object *obj, OptionalContent *oc, } OCDisplayNode::OCDisplayNode() { - name = NULL; - nameLen = 0; + name = new TextString(); ocg = NULL; children = NULL; } OCDisplayNode::OCDisplayNode(GString *nameA) { - int i; - - if ((nameA->getChar(0) & 0xff) == 0xfe && - (nameA->getChar(1) & 0xff) == 0xff) { - nameLen = (nameA->getLength() - 2) / 2; - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = ((nameA->getChar(2 + 2*i) & 0xff) << 8) | - (nameA->getChar(3 + 2*i) & 0xff); - } - } else { - nameLen = nameA->getLength(); - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = pdfDocEncoding[nameA->getChar(i) & 0xff]; - } - } + name = new TextString(nameA); ocg = NULL; children = NULL; } OCDisplayNode::OCDisplayNode(OptionalContentGroup *ocgA) { - nameLen = ocgA->getNameLength(); - if (nameLen) { - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - memcpy(name, ocgA->getName(), nameLen * sizeof(Unicode)); - } else { - name = NULL; - } + name = new TextString(ocgA->name); ocg = ocgA; children = NULL; } @@ -482,12 +457,20 @@ GList *OCDisplayNode::takeChildren() { } OCDisplayNode::~OCDisplayNode() { - gfree(name); + delete name; if (children) { deleteGList(children, OCDisplayNode); } } +Unicode *OCDisplayNode::getName() { + return name->getUnicode(); +} + +int OCDisplayNode::getNameLength() { + return name->getLength(); +} + int OCDisplayNode::getNumChildren() { if (!children) { return 0; diff --git a/xpdf/OptionalContent.h b/xpdf/OptionalContent.h index 82d3e0e..e2ea244 100644 --- a/xpdf/OptionalContent.h +++ b/xpdf/OptionalContent.h @@ -2,7 +2,7 @@ // // OptionalContent.h // -// Copyright 2008 Glyph & Cog, LLC +// Copyright 2008-2013 Glyph & Cog, LLC // //======================================================================== @@ -22,6 +22,7 @@ class GString; class GList; class PDFDoc; +class TextString; class XRef; class OptionalContentGroup; class OCDisplayNode; @@ -78,8 +79,8 @@ public: GBool matches(Ref *refA); - Unicode *getName() { return name; } - int getNameLength() { return nameLen; } + Unicode *getName(); + int getNameLength(); OCUsageState getViewState() { return viewState; } OCUsageState getPrintState() { return printState; } GBool getState() { return state; } @@ -87,15 +88,16 @@ public: private: - OptionalContentGroup(Ref *refA, Unicode *nameA, int nameLenA, + OptionalContentGroup(Ref *refA, TextString *nameA, OCUsageState viewStateA, OCUsageState printStateA); Ref ref; - Unicode *name; - int nameLen; + TextString *name; OCUsageState viewState, // suggested state when viewing printState; // suggested state when printing GBool state; // current state (on/off) + + friend class OCDisplayNode; }; //------------------------------------------------------------------------ @@ -108,8 +110,8 @@ public: OCDisplayNode(); ~OCDisplayNode(); - Unicode *getName() { return name; } - int getNameLength() { return nameLen; } + Unicode *getName(); + int getNameLength(); OptionalContentGroup *getOCG() { return ocg; } int getNumChildren(); OCDisplayNode *getChild(int idx); @@ -122,8 +124,7 @@ private: void addChildren(GList *childrenA); GList *takeChildren(); - Unicode *name; // display name (may be NULL) - int nameLen; + TextString *name; // display name OptionalContentGroup *ocg; // NULL for display labels GList *children; // NULL if there are no children // [OCDisplayNode] diff --git a/xpdf/Outline.cc b/xpdf/Outline.cc index 30ca85d..e87f61e 100644 --- a/xpdf/Outline.cc +++ b/xpdf/Outline.cc @@ -2,7 +2,7 @@ // // Outline.cc // -// Copyright 2002-2003 Glyph & Cog, LLC +// Copyright 2002-2013 Glyph & Cog, LLC // //======================================================================== @@ -17,7 +17,7 @@ #include "GList.h" #include "Error.h" #include "Link.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "Outline.h" //------------------------------------------------------------------------ @@ -32,7 +32,7 @@ Outline::Outline(Object *outlineObj, XRef *xref) { outlineObj->dictLookupNF("First", &first); outlineObj->dictLookupNF("Last", &last); if (first.isRef() && last.isRef()) { - items = OutlineItem::readItemList(&first, &last, xref); + items = OutlineItem::readItemList(&first, &last, NULL, xref); } first.free(); last.free(); @@ -46,35 +46,18 @@ Outline::~Outline() { //------------------------------------------------------------------------ -OutlineItem::OutlineItem(Dict *dict, XRef *xrefA) { +OutlineItem::OutlineItem(Object *itemRefA, Dict *dict, + OutlineItem *parentA, XRef *xrefA) { Object obj1; - GString *s; - int i; xref = xrefA; title = NULL; action = NULL; kids = NULL; + parent = parentA; if (dict->lookup("Title", &obj1)->isString()) { - s = obj1.getString(); - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - titleLen = (s->getLength() - 2) / 2; - title = (Unicode *)gmallocn(titleLen, sizeof(Unicode)); - for (i = 0; i < titleLen; ++i) { - title[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } - } else { - titleLen = s->getLength(); - title = (Unicode *)gmallocn(titleLen, sizeof(Unicode)); - for (i = 0; i < titleLen; ++i) { - title[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } - } - } else { - titleLen = 0; + title = new TextString(obj1.getString()); } obj1.free(); @@ -88,6 +71,7 @@ OutlineItem::OutlineItem(Dict *dict, XRef *xrefA) { } obj1.free(); + itemRefA->copy(&itemRef); dict->lookupNF("First", &firstRef); dict->lookupNF("Last", &lastRef); dict->lookupNF("Next", &nextRef); @@ -104,22 +88,24 @@ OutlineItem::OutlineItem(Dict *dict, XRef *xrefA) { OutlineItem::~OutlineItem() { close(); if (title) { - gfree(title); + delete title; } if (action) { delete action; } + itemRef.free(); firstRef.free(); lastRef.free(); nextRef.free(); } GList *OutlineItem::readItemList(Object *firstItemRef, Object *lastItemRef, - XRef *xrefA) { + OutlineItem *parentA, XRef *xrefA) { GList *items; - OutlineItem *item; + OutlineItem *item, *sibling; Object obj; - Object *p, *refObj; + Object *p; + OutlineItem *ancestor; int i; items = new GList(); @@ -132,8 +118,36 @@ GList *OutlineItem::readItemList(Object *firstItemRef, Object *lastItemRef, obj.free(); break; } - item = new OutlineItem(obj.getDict(), xrefA); + item = new OutlineItem(p, obj.getDict(), parentA, xrefA); obj.free(); + + // check for loops with parents + for (ancestor = parentA; ancestor; ancestor = ancestor->parent) { + if (p->getRefNum() == ancestor->itemRef.getRefNum() && + p->getRefGen() == ancestor->itemRef.getRefGen()) { + error(errSyntaxError, -1, "Loop detected in outline"); + break; + } + } + if (ancestor) { + delete item; + break; + } + + // check for loops with siblings + for (i = 0; i < items->getLength(); ++i) { + sibling = (OutlineItem *)items->get(i); + if (p->getRefNum() == sibling->itemRef.getRefNum() && + p->getRefGen() == sibling->itemRef.getRefGen()) { + error(errSyntaxError, -1, "Loop detected in outline"); + break; + } + } + if (i < items->getLength()) { + delete item; + break; + } + items->append(item); if (p->getRefNum() == lastItemRef->getRef().num && p->getRefGen() == lastItemRef->getRef().gen) { @@ -143,23 +157,13 @@ GList *OutlineItem::readItemList(Object *firstItemRef, Object *lastItemRef, if (!p->isRef()) { break; } - for (i = 0; i < items->getLength(); ++i) { - refObj = (i == 0) ? firstItemRef - : &((OutlineItem *)items->get(i - 1))->nextRef; - if (refObj->getRefNum() == p->getRefNum() && - refObj->getRefGen() == p->getRefGen()) { - error(errSyntaxError, -1, "Loop detected in outline item list"); - p = NULL; - break; - } - } } while (p); return items; } void OutlineItem::open() { if (!kids) { - kids = readItemList(&firstRef, &lastRef, xref); + kids = readItemList(&firstRef, &lastRef, this, xref); } } @@ -169,3 +173,11 @@ void OutlineItem::close() { kids = NULL; } } + +Unicode *OutlineItem::getTitle() { + return title ? title->getUnicode() : (Unicode *)NULL; +} + +int OutlineItem::getTitleLength() { + return title ? title->getLength() : 0; +} diff --git a/xpdf/Outline.h b/xpdf/Outline.h index f38f8d1..a9c2089 100644 --- a/xpdf/Outline.h +++ b/xpdf/Outline.h @@ -2,7 +2,7 @@ // // Outline.h // -// Copyright 2002-2003 Glyph & Cog, LLC +// Copyright 2002-2013 Glyph & Cog, LLC // //======================================================================== @@ -22,6 +22,7 @@ class GString; class GList; class XRef; class LinkAction; +class TextString; //------------------------------------------------------------------------ @@ -44,17 +45,18 @@ private: class OutlineItem { public: - OutlineItem(Dict *dict, XRef *xrefA); + OutlineItem(Object *itemRefA, Dict *dict, OutlineItem *parentA, XRef *xrefA); ~OutlineItem(); static GList *readItemList(Object *firstItemRef, Object *lastItemRef, - XRef *xrefA); + OutlineItem *parentA, XRef *xrefA); void open(); void close(); - Unicode *getTitle() { return title; } - int getTitleLength() { return titleLen; } + Unicode *getTitle(); + int getTitleLength(); + TextString *getTitleTextString() { return title; } LinkAction *getAction() { return action; } GBool isOpen() { return startsOpen; } GBool hasKids() { return firstRef.isRef(); } @@ -63,14 +65,15 @@ public: private: XRef *xref; - Unicode *title; - int titleLen; + TextString *title; // may be NULL LinkAction *action; + Object itemRef; Object firstRef; Object lastRef; Object nextRef; GBool startsOpen; GList *kids; // NULL unless this item is open [OutlineItem] + OutlineItem *parent; }; #endif diff --git a/xpdf/OutputDev.cc b/xpdf/OutputDev.cc index d2ef286..2d17547 100644 --- a/xpdf/OutputDev.cc +++ b/xpdf/OutputDev.cc @@ -43,6 +43,11 @@ void OutputDev::cvtDevToUser(double dx, double dy, double *ux, double *uy) { *uy = defICTM[1] * dx + defICTM[3] * dy + defICTM[5]; } +void OutputDev::cvtUserToDev(double ux, double uy, double *dx, double *dy) { + *dx = defCTM[0] * ux + defCTM[2] * uy + defCTM[4]; + *dy = defCTM[1] * ux + defCTM[3] * uy + defCTM[5]; +} + void OutputDev::cvtUserToDev(double ux, double uy, int *dx, int *dy) { *dx = (int)(defCTM[0] * ux + defCTM[2] * uy + defCTM[4] + 0.5); *dy = (int)(defCTM[1] * ux + defCTM[3] * uy + defCTM[5] + 0.5); @@ -78,14 +83,10 @@ GBool OutputDev::beginType3Char(GfxState *state, double x, double y, void OutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { - int i, j; - + GBool inlineImg, GBool interpolate) { if (inlineImg) { str->reset(); - j = height * ((width + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width + 7) / 8)); str->close(); } } @@ -93,21 +94,17 @@ void OutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, void OutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { - drawImageMask(state, ref, str, width, height, invert, inlineImg); + GBool inlineImg, GBool interpolate) { + drawImageMask(state, ref, str, width, height, invert, inlineImg, interpolate); } void OutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { - int i, j; - + int *maskColors, GBool inlineImg, GBool interpolate) { if (inlineImg) { str->reset(); - j = height * ((width * colorMap->getNumPixelComps() * - colorMap->getBits() + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8)); str->close(); } } @@ -117,8 +114,9 @@ void OutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert) { - drawImage(state, ref, str, width, height, colorMap, NULL, gFalse); + GBool maskInvert, GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, + interpolate); } void OutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, @@ -126,8 +124,10 @@ void OutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap) { - drawImage(state, ref, str, width, height, colorMap, NULL, gFalse); + GfxImageColorMap *maskColorMap, + GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, + interpolate); } #if OPI_SUPPORT diff --git a/xpdf/OutputDev.h b/xpdf/OutputDev.h index 138aa34..759e356 100644 --- a/xpdf/OutputDev.h +++ b/xpdf/OutputDev.h @@ -112,6 +112,7 @@ public: // Convert between device and user coordinates. virtual void cvtDevToUser(double dx, double dy, double *ux, double *uy); + virtual void cvtUserToDev(double ux, double uy, double *dx, double *dy); virtual void cvtUserToDev(double ux, double uy, int *dx, int *dy); double *getDefCTM() { return defCTM; } @@ -161,7 +162,7 @@ public: virtual void stroke(GfxState *state) {} virtual void fill(GfxState *state) {} virtual void eoFill(GfxState *state) {} - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -201,25 +202,26 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap); + GfxImageColorMap *maskColorMap, + GBool interpolate); #if OPI_SUPPORT //----- OPI functions diff --git a/xpdf/PDFCore.cc b/xpdf/PDFCore.cc index 6435815..34b6483 100644 --- a/xpdf/PDFCore.cc +++ b/xpdf/PDFCore.cc @@ -2,7 +2,7 @@ // // PDFCore.cc // -// Copyright 2004 Glyph & Cog, LLC +// Copyright 2004-2013 Glyph & Cog, LLC // //======================================================================== @@ -100,7 +100,8 @@ PDFCore::PDFCore(SplashColorMode colorModeA, int bitmapRowPadA, selectULY = selectLRY = 0; dragging = gFalse; lastDragLeft = lastDragTop = gTrue; - selectXorColor[0] = selectXorColor[1] = selectXorColor[2] = 0; + selectXorColor[0] = selectXorColor[1] = selectXorColor[2] = + reverseVideoA ? 0xff : 0x00; splashColorXor(selectXorColor, paperColorA); historyCur = pdfHistorySize - 1; @@ -128,7 +129,11 @@ PDFCore::~PDFCore() { } for (i = 0; i < pdfHistorySize; ++i) { if (history[i].fileName) { +#ifdef _WIN32 + delete[] history[i].fileName; +#else delete history[i].fileName; +#endif } } gfree(pageY); @@ -147,7 +152,7 @@ int PDFCore::loadFile(GString *fileName, GString *ownerPassword, return err; } -#ifdef WIN32 +#ifdef _WIN32 int PDFCore::loadFile(wchar_t *fileName, int fileNameLen, GString *ownerPassword, GString *userPassword) { int err; @@ -423,6 +428,7 @@ void PDFCore::update(int topPageA, int scrollXA, int scrollYA, // check for changes to the PDF file if ((force || (!continuousMode && topPage != topPageA)) && + doc->getFileName() && checkForNewFile()) { if (loadFile(doc->getFileName()) == errNone) { if (topPageA > doc->getNumPages()) { @@ -758,13 +764,30 @@ void PDFCore::update(int topPageA, int scrollXA, int scrollYA, } hist = &history[historyCur]; if (hist->fileName) { +#ifdef _WIN32 + delete[] hist->fileName; +#else delete hist->fileName; +#endif + } +#ifdef _WIN32 + if (doc->getFileNameU()) { + hist->fileName = (wchar_t *)gmallocn(MAX_PATH + 1, sizeof(wchar_t)); + if (GetFullPathNameW(doc->getFileNameU(), MAX_PATH + 1, + hist->fileName, NULL) == 0) { + delete[] hist->fileName; + hist->fileName = NULL; + } + } else { + hist->fileName = NULL; } +#else if (doc->getFileName()) { hist->fileName = doc->getFileName()->copy(); } else { hist->fileName = NULL; } +#endif hist->page = topPage; if (historyBLen < pdfHistorySize) { ++historyBLen; @@ -807,6 +830,7 @@ void PDFCore::addPage(int pg, int rot) { void PDFCore::needTile(PDFCorePage *page, int x, int y) { PDFCoreTile *tile; + TextOutputControl textOutCtrl; TextOutputDev *textOut; int xDest, yDest, sliceW, sliceH; int i; @@ -893,7 +917,8 @@ void PDFCore::needTile(PDFCorePage *page, int x, int y) { page->links = doc->getLinks(page->page); } if (!page->text) { - if ((textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse))) { + textOutCtrl.mode = textOutPhysLayout; + if ((textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse))) { doc->displayPage(textOut, page->page, dpi, dpi, rotate, gFalse, gTrue, gFalse); page->text = textOut->takeText(); @@ -977,11 +1002,27 @@ GBool PDFCore::goForward() { } --historyFLen; ++historyBLen; - if (!doc || history[historyCur].fileName->cmp(doc->getFileName()) != 0) { + if (!history[historyCur].fileName) { + return gFalse; + } +#ifdef _WIN32 + if (!doc || + !doc->getFileNameU() || + wcscmp(history[historyCur].fileName, doc->getFileNameU()) != 0) { + if (loadFile(history[historyCur].fileName, + wcslen(history[historyCur].fileName)) != errNone) { + return gFalse; + } + } +#else + if (!doc || + !doc->getFileName() || + history[historyCur].fileName->cmp(doc->getFileName()) != 0) { if (loadFile(history[historyCur].fileName) != errNone) { return gFalse; } } +#endif pg = history[historyCur].page; update(pg, scrollX, continuousMode ? -1 : scrollY, zoom, rotate, gFalse, gFalse, gTrue); @@ -999,11 +1040,27 @@ GBool PDFCore::goBackward() { } --historyBLen; ++historyFLen; - if (!doc || history[historyCur].fileName->cmp(doc->getFileName()) != 0) { + if (!history[historyCur].fileName) { + return gFalse; + } +#ifdef _WIN32 + if (!doc || + !doc->getFileNameU() || + wcscmp(history[historyCur].fileName, doc->getFileNameU()) != 0) { + if (loadFile(history[historyCur].fileName, + wcslen(history[historyCur].fileName)) != errNone) { + return gFalse; + } + } +#else + if (!doc || + !doc->getFileName() || + history[historyCur].fileName->cmp(doc->getFileName()) != 0) { if (loadFile(history[historyCur].fileName) != errNone) { return gFalse; } } +#endif pg = history[historyCur].page; update(pg, scrollX, continuousMode ? -1 : scrollY, zoom, rotate, gFalse, gFalse, gTrue); @@ -1615,6 +1672,7 @@ GBool PDFCore::getSelection(int *pg, double *ulx, double *uly, GString *PDFCore::extractText(int pg, double xMin, double yMin, double xMax, double yMax) { PDFCorePage *page; + TextOutputControl textOutCtrl; TextOutputDev *textOut; int x0, y0, x1, y1, t; GString *s; @@ -1633,7 +1691,8 @@ GString *PDFCore::extractText(int pg, double xMin, double yMin, } s = page->text->getText(x0, y0, x1, y1); } else { - textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse); + textOutCtrl.mode = textOutPhysLayout; + textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse); if (textOut->isOk()) { doc->displayPage(textOut, pg, dpi, dpi, rotate, gFalse, gTrue, gFalse); textOut->cvtUserToDev(xMin, yMin, &x0, &y0); @@ -1675,10 +1734,10 @@ GBool PDFCore::find(char *s, GBool caseSensitive, GBool next, GBool backward, GBool PDFCore::findU(Unicode *u, int len, GBool caseSensitive, GBool next, GBool backward, GBool wholeWord, GBool onePageOnly) { + TextOutputControl textOutCtrl; TextOutputDev *textOut; double xMin, yMin, xMax, yMax; PDFCorePage *page; - PDFCoreTile *tile; int pg; GBool startAtTop, startAtLast, stopAtLast; @@ -1721,7 +1780,8 @@ GBool PDFCore::findU(Unicode *u, int len, GBool caseSensitive, if (!onePageOnly) { // search following/previous pages - textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse); + textOutCtrl.mode = textOutPhysLayout; + textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse); if (!textOut->isOk()) { delete textOut; goto notFound; @@ -1791,7 +1851,6 @@ GBool PDFCore::findU(Unicode *u, int len, GBool caseSensitive, // found: change the selection found: - tile = (PDFCoreTile *)page->tiles->get(0); setSelection(pg, (int)floor(xMin), (int)floor(yMin), (int)ceil(xMax), (int)ceil(yMax)); diff --git a/xpdf/PDFCore.h b/xpdf/PDFCore.h index 264756f..1be1010 100644 --- a/xpdf/PDFCore.h +++ b/xpdf/PDFCore.h @@ -100,7 +100,11 @@ public: //------------------------------------------------------------------------ struct PDFHistory { +#ifdef _WIN32 + wchar_t *fileName; +#else GString *fileName; +#endif int page; }; @@ -125,7 +129,7 @@ public: virtual int loadFile(GString *fileName, GString *ownerPassword = NULL, GString *userPassword = NULL); -#ifdef WIN32 +#ifdef _WIN32 // Load a new file. Returns pdfOk or error code. virtual int loadFile(wchar_t *fileName, int fileNameLen, GString *ownerPassword = NULL, diff --git a/xpdf/PDFDoc.cc b/xpdf/PDFDoc.cc index b20ef2c..5126289 100644 --- a/xpdf/PDFDoc.cc +++ b/xpdf/PDFDoc.cc @@ -16,7 +16,7 @@ #include <stdlib.h> #include <stddef.h> #include <string.h> -#ifdef WIN32 +#ifdef _WIN32 # include <windows.h> #endif #include "GString.h" @@ -52,7 +52,7 @@ PDFDoc::PDFDoc(GString *fileNameA, GString *ownerPassword, GString *userPassword, PDFCore *coreA) { Object obj; GString *fileName1, *fileName2; -#ifdef WIN32 +#ifdef _WIN32 int n, i; #endif @@ -71,7 +71,7 @@ PDFDoc::PDFDoc(GString *fileNameA, GString *ownerPassword, optContent = NULL; fileName = fileNameA; -#ifdef WIN32 +#ifdef _WIN32 n = fileName->getLength(); fileNameU = (wchar_t *)gmallocn(n + 1, sizeof(wchar_t)); for (i = 0; i < n; ++i) { @@ -114,7 +114,7 @@ PDFDoc::PDFDoc(GString *fileNameA, GString *ownerPassword, ok = setup(ownerPassword, userPassword); } -#ifdef WIN32 +#ifdef _WIN32 PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword, GString *userPassword, PDFCore *coreA) { OSVERSIONINFO version; @@ -169,7 +169,7 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword, PDFDoc::PDFDoc(BaseStream *strA, GString *ownerPassword, GString *userPassword, PDFCore *coreA) { -#ifdef WIN32 +#ifdef _WIN32 int n, i; #endif @@ -178,7 +178,7 @@ PDFDoc::PDFDoc(BaseStream *strA, GString *ownerPassword, core = coreA; if (strA->getFileName()) { fileName = strA->getFileName()->copy(); -#ifdef WIN32 +#ifdef _WIN32 n = fileName->getLength(); fileNameU = (wchar_t *)gmallocn(n + 1, sizeof(wchar_t)); for (i = 0; i < n; ++i) { @@ -188,7 +188,7 @@ PDFDoc::PDFDoc(BaseStream *strA, GString *ownerPassword, #endif } else { fileName = NULL; -#ifdef WIN32 +#ifdef _WIN32 fileNameU = NULL; #endif } @@ -231,6 +231,7 @@ GBool PDFDoc::setup(GString *ownerPassword, GString *userPassword) { // read the optional content info optContent = new OptionalContent(this); + // done return gTrue; } @@ -294,7 +295,7 @@ PDFDoc::~PDFDoc() { if (fileName) { delete fileName; } -#ifdef WIN32 +#ifdef _WIN32 if (fileNameU) { gfree(fileNameU); } @@ -309,10 +310,8 @@ void PDFDoc::checkHeader() { int i; pdfVersion = 0; - for (i = 0; i < headerSearchSize; ++i) { - hdrBuf[i] = str->getChar(); - } - hdrBuf[headerSearchSize] = '\0'; + memset(hdrBuf, 0, headerSearchSize + 1); + str->getBlock(hdrBuf, headerSearchSize); for (i = 0; i < headerSearchSize - 5; ++i) { if (!strncmp(&hdrBuf[i], "%PDF-", 5)) { break; @@ -455,15 +454,16 @@ GBool PDFDoc::isLinearized() { GBool PDFDoc::saveAs(GString *name) { FILE *f; - int c; + char buf[4096]; + int n; if (!(f = fopen(name->getCString(), "wb"))) { error(errIO, -1, "Couldn't open file '{0:t}'", name); return gFalse; } str->reset(); - while ((c = str->getChar()) != EOF) { - fputc(c, f); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); } str->close(); fclose(f); @@ -482,7 +482,7 @@ GBool PDFDoc::saveEmbeddedFile(int idx, char *path) { return ret; } -#ifdef WIN32 +#ifdef _WIN32 GBool PDFDoc::saveEmbeddedFile(int idx, wchar_t *path, int pathLen) { FILE *f; OSVERSIONINFO version; @@ -518,14 +518,15 @@ GBool PDFDoc::saveEmbeddedFile(int idx, wchar_t *path, int pathLen) { GBool PDFDoc::saveEmbeddedFile2(int idx, FILE *f) { Object strObj; - int c; + char buf[4096]; + int n; if (!catalog->getEmbeddedFileStreamObj(idx, &strObj)) { return gFalse; } strObj.streamReset(); - while ((c = strObj.streamGetChar()) != EOF) { - fputc(c, f); + while ((n = strObj.streamGetBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); } strObj.streamClose(); strObj.free(); @@ -535,24 +536,28 @@ GBool PDFDoc::saveEmbeddedFile2(int idx, FILE *f) { char *PDFDoc::getEmbeddedFileMem(int idx, int *size) { Object strObj; char *buf; - int bufSize, len, c; + int bufSize, sizeInc, n; if (!catalog->getEmbeddedFileStreamObj(idx, &strObj)) { return NULL; } strObj.streamReset(); - bufSize = 1024; - buf = (char *)gmalloc(bufSize); - len = 0; - while ((c = strObj.streamGetChar()) != EOF) { - if (len == bufSize) { - bufSize *= 2; - buf = (char *)grealloc(buf, bufSize); + bufSize = 0; + buf = NULL; + do { + sizeInc = bufSize ? bufSize : 1024; + if (bufSize > INT_MAX - sizeInc) { + error(errIO, -1, "embedded file is too large"); + *size = 0; + return NULL; } - buf[len++] = (char)c; - } + buf = (char *)grealloc(buf, bufSize + sizeInc); + n = strObj.streamGetBlock(buf + bufSize, sizeInc); + bufSize += n; + } while (n == sizeInc); strObj.streamClose(); strObj.free(); - *size = len; + *size = bufSize; return buf; } + diff --git a/xpdf/PDFDoc.h b/xpdf/PDFDoc.h index 94fcfb7..509c1d5 100644 --- a/xpdf/PDFDoc.h +++ b/xpdf/PDFDoc.h @@ -39,7 +39,7 @@ public: PDFDoc(GString *fileNameA, GString *ownerPassword = NULL, GString *userPassword = NULL, PDFCore *coreA = NULL); -#ifdef WIN32 +#ifdef _WIN32 PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword = NULL, GString *userPassword = NULL, PDFCore *coreA = NULL); #endif @@ -55,7 +55,7 @@ public: // Get file name. GString *getFileName() { return fileName; } -#ifdef WIN32 +#ifdef _WIN32 wchar_t *getFileNameU() { return fileNameU; } #endif @@ -172,11 +172,12 @@ public: int getEmbeddedFileNameLength(int idx) { return catalog->getEmbeddedFileNameLength(idx); } GBool saveEmbeddedFile(int idx, char *path); -#ifdef WIN32 +#ifdef _WIN32 GBool saveEmbeddedFile(int idx, wchar_t *path, int pathLen); #endif char *getEmbeddedFileMem(int idx, int *size); + private: GBool setup(GString *ownerPassword, GString *userPassword); @@ -187,7 +188,7 @@ private: GBool saveEmbeddedFile2(int idx, FILE *f); GString *fileName; -#ifdef WIN32 +#ifdef _WIN32 wchar_t *fileNameU; #endif FILE *file; diff --git a/xpdf/PSOutputDev.cc b/xpdf/PSOutputDev.cc index 55e576b..9521f6c 100644 --- a/xpdf/PSOutputDev.cc +++ b/xpdf/PSOutputDev.cc @@ -2,7 +2,7 @@ // // PSOutputDev.cc // -// Copyright 1996-2003 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -39,6 +39,8 @@ #include "XRef.h" #include "PreScanOutputDev.h" #include "CharCodeToUnicode.h" +#include "Form.h" +#include "TextString.h" #if HAVE_SPLASH # include "Splash.h" # include "SplashBitmap.h" @@ -57,11 +59,6 @@ #endif //------------------------------------------------------------------------ - -// Max size of a slice when rasterizing pages, in pixels. -#define rasterizationSliceSize 20000000 - -//------------------------------------------------------------------------ // PostScript prolog and setup //------------------------------------------------------------------------ @@ -85,24 +82,29 @@ static const char *prolog[] = { " } for", "~123sn", "/pdfSetup {", + " /pdfDuplex exch def", " /setpagedevice where {", " pop 2 dict begin", " /Policies 1 dict dup begin /PageSize 6 def end def", - " { /Duplex true def } if", + " pdfDuplex { /Duplex true def } if", " currentdict end setpagedevice", - " } {", - " pop", - " } ifelse", + " } if", + " /pdfPageW 0 def", + " /pdfPageH 0 def", "} def", "/pdfSetupPaper {", - " 2 array astore", - " /setpagedevice where {", - " pop 2 dict begin", - " /PageSize exch def", - " /ImagingBBox null def", - " currentdict end setpagedevice", + " 2 copy pdfPageH ne exch pdfPageW ne or {", + " /pdfPageH exch def", + " /pdfPageW exch def", + " /setpagedevice where {", + " pop 3 dict begin", + " /PageSize [pdfPageW pdfPageH] def", + " pdfDuplex { /Duplex true def } if", + " /ImagingBBox null def", + " currentdict end setpagedevice", + " } if", " } {", - " pop", + " pop pop", " } ifelse", "} def", "~1sn", @@ -571,17 +573,14 @@ static const char *prolog[] = { "} def", "~23sn", "/pdfImM { fCol imagemask skipEOD } def", - "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def", - "/pdfImClip {", - " gsave", - " 0 2 4 index length 1 sub {", - " dup 4 index exch 2 copy", - " get 5 index div put", - " 1 add 3 index exch 2 copy", - " get 3 index div put", - " } for", - " pop pop rectclip", + "/pr {", + " 4 2 roll exch 5 index div exch 4 index div moveto", + " exch 3 index div dup 0 rlineto", + " exch 2 index div 0 exch rlineto", + " neg 0 rlineto", + " closepath", "} def", + "/pdfImClip { gsave clip } def", "/pdfImClipEnd { grestore } def", "~23sn", "% shading operators", @@ -736,6 +735,19 @@ static const char *prolog[] = { NULL }; +static const char *minLineWidthProlog[] = { + "/pdfDist { dup dtransform dup mul exch dup mul add 0.5 mul sqrt } def", + "/pdfIDist { dup idtransform dup mul exch dup mul add 0.5 mul sqrt } def", + "/pdfMinLineDist pdfMinLineWidth pdfDist def", + "/setlinewidth {", + " dup pdfDist pdfMinLineDist lt {", + " pop pdfMinLineDist pdfIDist", + " } if", + " setlinewidth", + "} bind def", + NULL +}; + static const char *cmapProlog[] = { "/CIDInit /ProcSet findresource begin", "10 dict begin", @@ -809,25 +821,68 @@ static PSSubstFont psBase14SubstFonts[14] = { {"ZapfDingbats", 0} }; -// Mapping from Type 1/1C font file to PS font name. -struct PST1FontName { - Ref fontFileID; - GString *psName; // PostScript font name used for this - // embedded font file -}; +class PSFontInfo { +public: + + PSFontInfo(Ref fontIDA) + { fontID = fontIDA; ff = NULL; } -// Info for 8-bit fonts -struct PSFont8Info { Ref fontID; - int *codeToGID; // code-to-GID mapping for TrueType fonts + PSFontFileInfo *ff; // pointer to font file info; NULL indicates + // font mapping failed }; -// Encoding info for substitute 16-bit font -struct PSFont16Enc { - Ref fontID; - GString *enc; // NULL means font wasn't correctly substituted +enum PSFontFileLocation { + psFontFileResident, + psFontFileEmbedded, + psFontFileExternal }; +class PSFontFileInfo { +public: + + PSFontFileInfo(GString *psNameA, GfxFontType typeA, + PSFontFileLocation locA); + ~PSFontFileInfo(); + + GString *psName; // name under which font is defined + GfxFontType type; // font type + PSFontFileLocation loc; // font location + Ref embFontID; // object ID for the embedded font file + // (for all embedded fonts) + GString *extFileName; // external font file path + // (for all external fonts) + GString *encoding; // encoding name (for resident CID fonts) + int *codeToGID; // mapping from code/CID to GID + // (for TrueType, OpenType-TrueType, and + // CID OpenType-CFF fonts) + int codeToGIDLen; // length of codeToGID array +}; + +PSFontFileInfo::PSFontFileInfo(GString *psNameA, GfxFontType typeA, + PSFontFileLocation locA) { + psName = psNameA; + type = typeA; + loc = locA; + embFontID.num = embFontID.gen = -1; + extFileName = NULL; + encoding = NULL; + codeToGID = NULL; +} + +PSFontFileInfo::~PSFontFileInfo() { + delete psName; + if (extFileName) { + delete extFileName; + } + if (encoding) { + delete encoding; + } + if (codeToGID) { + gfree(codeToGID); + } +} + //------------------------------------------------------------------------ // process colors //------------------------------------------------------------------------ @@ -998,11 +1053,8 @@ PSOutputDev::PSOutputDev(char *fileName, PDFDoc *docA, customCodeCbk = customCodeCbkA; customCodeCbkData = customCodeCbkDataA; - fontIDs = NULL; - fontNames = new GHash(gTrue); - t1FontNames = NULL; - font8Info = NULL; - font16Enc = NULL; + fontInfo = new GList(); + fontFileInfo = new GHash(); imgIDs = NULL; formIDs = NULL; xobjStack = NULL; @@ -1019,7 +1071,7 @@ PSOutputDev::PSOutputDev(char *fileName, PDFDoc *docA, } else if (fileName[0] == '|') { fileTypeA = psPipe; #ifdef HAVE_POPEN -#ifndef WIN32 +#ifndef _WIN32 signal(SIGPIPE, (SignalFunc)SIG_IGN); #endif if (!(f = popen(fileName + 1, "w"))) { @@ -1060,11 +1112,8 @@ PSOutputDev::PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA, customCodeCbk = customCodeCbkA; customCodeCbkData = customCodeCbkDataA; - fontIDs = NULL; - fontNames = new GHash(gTrue); - t1FontNames = NULL; - font8Info = NULL; - font16Enc = NULL; + fontInfo = new GList(); + fontFileInfo = new GHash(); imgIDs = NULL; formIDs = NULL; xobjStack = NULL; @@ -1088,6 +1137,7 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, Page *page; PDFRectangle *box; PSOutPaperSize *size; + PSFontFileInfo *ff; GList *names; int pg, w, h, i; @@ -1118,8 +1168,13 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, pg <= lastPage && pg <= catalog->getNumPages(); ++pg) { page = catalog->getPage(pg); - w = (int)ceil(page->getMediaWidth()); - h = (int)ceil(page->getMediaHeight()); + if (globalParams->getPSUseCropBoxAsPage()) { + w = (int)ceil(page->getCropWidth()); + h = (int)ceil(page->getCropHeight()); + } else { + w = (int)ceil(page->getMediaWidth()); + h = (int)ceil(page->getMediaHeight()); + } for (i = 0; i < paperSizes->getLength(); ++i) { size = (PSOutPaperSize *)paperSizes->get(i); if (size->w == w && size->h == h) { @@ -1160,25 +1215,21 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, clipLLX0 = clipLLY0 = 0; clipURX0 = clipURY0 = -1; - // initialize fontIDs and fontNames lists - fontIDSize = 64; - fontIDLen = 0; - fontIDs = (Ref *)gmallocn(fontIDSize, sizeof(Ref)); + // initialize font lists, etc. for (i = 0; i < 14; ++i) { - fontNames->add(new GString(psBase14SubstFonts[i].psName), 1); + ff = new PSFontFileInfo(new GString(psBase14SubstFonts[i].psName), + fontType1, psFontFileResident); + fontFileInfo->add(ff->psName, ff); } names = globalParams->getPSResidentFonts(); for (i = 0; i < names->getLength(); ++i) { - fontNames->add((GString *)names->get(i), 1); + if (!fontFileInfo->lookup((GString *)names->get(i))) { + ff = new PSFontFileInfo((GString *)names->get(i), fontType1, + psFontFileResident); + fontFileInfo->add(ff->psName, ff); + } } delete names; - t1FontNameSize = 64; - t1FontNameLen = 0; - t1FontNames = (PST1FontName *)gmallocn(t1FontNameSize, sizeof(PST1FontName)); - font8InfoLen = 0; - font8InfoSize = 0; - font16EncLen = 0; - font16EncSize = 0; imgIDLen = 0; imgIDSize = 0; formIDLen = 0; @@ -1224,7 +1275,6 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, PSOutputDev::~PSOutputDev() { PSOutCustomColor *cc; - int i; if (ok) { if (!manualCtrl) { @@ -1243,7 +1293,7 @@ PSOutputDev::~PSOutputDev() { #ifdef HAVE_POPEN else if (fileType == psPipe) { pclose((FILE *)outputStream); -#ifndef WIN32 +#ifndef _WIN32 signal(SIGPIPE, (SignalFunc)SIG_DFL); #endif } @@ -1255,30 +1305,8 @@ PSOutputDev::~PSOutputDev() { if (embFontList) { delete embFontList; } - if (fontIDs) { - gfree(fontIDs); - } - delete fontNames; - if (t1FontNames) { - for (i = 0; i < t1FontNameLen; ++i) { - delete t1FontNames[i].psName; - } - gfree(t1FontNames); - } - if (font8Info) { - for (i = 0; i < font8InfoLen; ++i) { - gfree(font8Info[i].codeToGID); - } - gfree(font8Info); - } - if (font16Enc) { - for (i = 0; i < font16EncLen; ++i) { - if (font16Enc[i].enc) { - delete font16Enc[i].enc; - } - } - gfree(font16Enc); - } + deleteGList(fontInfo, PSFontInfo); + deleteGHash(fontFileInfo, PSFontFileInfo); gfree(imgIDs); gfree(formIDs); if (xobjStack) { @@ -1291,6 +1319,16 @@ PSOutputDev::~PSOutputDev() { } } +GBool PSOutputDev::checkIO() { + if (fileType == psFile || fileType == psPipe || fileType == psStdout) { + if (ferror((FILE *)outputStream)) { + error(errIO, -1, "Error writing to PostScript file"); + return gFalse; + } + } + return gTrue; +} + void PSOutputDev::writeHeader(int firstPage, int lastPage, PDFRectangle *mediaBox, PDFRectangle *cropBox, int pageRotate) { @@ -1395,6 +1433,7 @@ void PSOutputDev::writeXpdfProcset() { GBool lev1, lev2, lev3, sep, nonSep; const char **p; const char *q; + double w; writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", xpdfVersion); writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright); @@ -1420,6 +1459,12 @@ void PSOutputDev::writeXpdfProcset() { writePSFmt("{0:s}\n", *p); } } + if ((w = globalParams->getPSMinLineWidth()) > 0) { + writePSFmt("/pdfMinLineWidth {0:.4g} def\n", w); + for (p = minLineWidthProlog; *p; ++p) { + writePSFmt("{0:s}\n", *p); + } + } writePS("%%EndResource\n"); if (level >= psLevel3) { @@ -1434,10 +1479,10 @@ void PSOutputDev::writeDocSetup(Catalog *catalog, Page *page; Dict *resDict; Annots *annots; - Object *acroForm; + Form *form; Object obj1, obj2, obj3; GString *s; - int pg, i; + int pg, i, j; if (mode == psModeForm) { // swap the form and xpdf dicts @@ -1464,23 +1509,22 @@ void PSOutputDev::writeDocSetup(Catalog *catalog, } delete annots; } - if ((acroForm = catalog->getAcroForm()) && acroForm->isDict()) { - if (acroForm->dictLookup("DR", &obj1)->isDict()) { - setupResources(obj1.getDict()); - } - obj1.free(); - if (acroForm->dictLookup("Fields", &obj1)->isArray()) { - for (i = 0; i < obj1.arrayGetLength(); ++i) { - if (obj1.arrayGet(i, &obj2)->isDict()) { - if (obj2.dictLookup("DR", &obj3)->isDict()) { - setupResources(obj3.getDict()); + if ((form = catalog->getForm())) { + for (i = 0; i < form->getNumFields(); ++i) { + form->getField(i)->getResources(&obj1); + if (obj1.isArray()) { + for (j = 0; j < obj1.arrayGetLength(); ++j) { + obj1.arrayGet(j, &obj2); + if (obj2.isDict()) { + setupResources(obj2.getDict()); } - obj3.free(); + obj2.free(); } - obj2.free(); + } else if (obj1.isDict()) { + setupResources(obj1.getDict()); } + obj1.free(); } - obj1.free(); } if (mode != psModeForm) { if (mode != psModeEPS && !manualCtrl) { @@ -1503,6 +1547,9 @@ void PSOutputDev::writeDocSetup(Catalog *catalog, delete s; } } + if (mode != psModeForm) { + writePS("end\n"); + } } void PSOutputDev::writePageTrailer() { @@ -1517,7 +1564,6 @@ void PSOutputDev::writeTrailer() { if (mode == psModeForm) { writePS("/Foo exch /Form defineresource pop\n"); } else { - writePS("end\n"); writePS("%%DocumentSuppliedResources:\n"); writePS(embFontList->getCString()); if (level == psLevel1Sep || level == psLevel2Sep || @@ -1554,14 +1600,14 @@ void PSOutputDev::writeTrailer() { } void PSOutputDev::setupResources(Dict *resDict) { - Object xObjDict, xObjRef, xObj, patDict, patRef, pat, resObj; + Object xObjDict, xObjRef, xObj, patDict, patRef, pat; + Object gsDict, gsRef, gs, smask, smaskGroup, resObj; Ref ref0, ref1; GBool skip; int i, j; setupFonts(resDict); setupImages(resDict); - setupForms(resDict); //----- recursively scan XObjects resDict->lookup("XObject", &xObjDict); @@ -1648,6 +1694,55 @@ void PSOutputDev::setupResources(Dict *resDict) { inType3Char = gFalse; } patDict.free(); + + //----- recursively scan SMask transparency groups in ExtGState dicts + resDict->lookup("ExtGState", &gsDict); + if (gsDict.isDict()) { + for (i = 0; i < gsDict.dictGetLength(); ++i) { + + // avoid infinite recursion on ExtGStates + skip = gFalse; + if ((gsDict.dictGetValNF(i, &gsRef)->isRef())) { + ref0 = gsRef.getRef(); + for (j = 0; j < xobjStack->getLength(); ++j) { + ref1 = *(Ref *)xobjStack->get(j); + if (ref1.num == ref0.num && ref1.gen == ref0.gen) { + skip = gTrue; + break; + } + } + if (!skip) { + xobjStack->append(&ref0); + } + } + if (!skip) { + + // process the ExtGState's SMask's transparency group's resource dict + if (gsDict.dictGetVal(i, &gs)->isDict()) { + if (gs.dictLookup("SMask", &smask)->isDict()) { + if (smask.dictLookup("G", &smaskGroup)->isStream()) { + smaskGroup.streamGetDict()->lookup("Resources", &resObj); + if (resObj.isDict()) { + setupResources(resObj.getDict()); + } + resObj.free(); + } + smaskGroup.free(); + } + smask.free(); + } + gs.free(); + } + + if (gsRef.isRef() && !skip) { + xobjStack->del(xobjStack->getLength() - 1); + } + gsRef.free(); + } + } + gsDict.free(); + + setupForms(resDict); } void PSOutputDev::setupFonts(Dict *resDict) { @@ -1681,8 +1776,8 @@ void PSOutputDev::setupFonts(Dict *resDict) { } void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { + PSFontInfo *fi; GfxFontLoc *fontLoc; - GString *psName; GBool subst; char buf[16]; UnicodeMap *uMap; @@ -1693,123 +1788,101 @@ void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { int i, j; // check if font is already set up - for (i = 0; i < fontIDLen; ++i) { - if (fontIDs[i].num == font->getID()->num && - fontIDs[i].gen == font->getID()->gen) { + for (i = 0; i < fontInfo->getLength(); ++i) { + fi = (PSFontInfo *)fontInfo->get(i); + if (fi->fontID.num == font->getID()->num && + fi->fontID.gen == font->getID()->gen) { return; } } - // add entry to fontIDs list - if (fontIDLen >= fontIDSize) { - fontIDSize += 64; - fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref)); - } - fontIDs[fontIDLen++] = *font->getID(); + // add fontInfo entry + fi = new PSFontInfo(*font->getID()); + fontInfo->append(fi); - psName = NULL; xs = ys = 1; subst = gFalse; if (font->getType() == fontType3) { - psName = GString::format("T3_{0:d}_{1:d}", - font->getID()->num, font->getID()->gen); - setupType3Font(font, psName, parentResDict); + fi->ff = setupType3Font(font, parentResDict); } else { - fontLoc = font->locateFont(xref, gTrue); - switch (fontLoc->locType) { - case gfxFontLocEmbedded: - switch (fontLoc->fontType) { - case fontType1: - // this assumes that the PS font name matches the PDF font name - psName = font->getEmbeddedFontName()->copy(); - setupEmbeddedType1Font(&fontLoc->embFontID, psName); - break; - case fontType1C: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedType1CFont(font, &fontLoc->embFontID, psName); - break; - case fontType1COT: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedOpenTypeT1CFont(font, &fontLoc->embFontID, psName); - break; - case fontTrueType: - case fontTrueTypeOT: - psName = makePSFontName(font, font->getID()); - setupEmbeddedTrueTypeFont(font, &fontLoc->embFontID, psName); - break; - case fontCIDType0C: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedCIDType0Font(font, &fontLoc->embFontID, psName); - break; - case fontCIDType2: - case fontCIDType2OT: - psName = makePSFontName(font, font->getID()); - //~ should check to see if font actually uses vertical mode - setupEmbeddedCIDTrueTypeFont(font, &fontLoc->embFontID, psName, gTrue); - break; - case fontCIDType0COT: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedOpenTypeCFFFont(font, &fontLoc->embFontID, psName); - break; - default: - break; - } - break; - case gfxFontLocExternal: - //~ add cases for external 16-bit fonts - switch (fontLoc->fontType) { - case fontType1: - if (font->getName()) { - // this assumes that the PS font name matches the PDF font name - psName = font->getName()->copy(); - } else { - //~ this won't work -- the PS font name won't match - psName = makePSFontName(font, font->getID()); + if ((fontLoc = font->locateFont(xref, gTrue))) { + switch (fontLoc->locType) { + case gfxFontLocEmbedded: + switch (fontLoc->fontType) { + case fontType1: + fi->ff = setupEmbeddedType1Font(font, &fontLoc->embFontID); + break; + case fontType1C: + fi->ff = setupEmbeddedType1CFont(font, &fontLoc->embFontID); + break; + case fontType1COT: + fi->ff = setupEmbeddedOpenTypeT1CFont(font, &fontLoc->embFontID); + break; + case fontTrueType: + case fontTrueTypeOT: + fi->ff = setupEmbeddedTrueTypeFont(font, &fontLoc->embFontID); + break; + case fontCIDType0C: + fi->ff = setupEmbeddedCIDType0Font(font, &fontLoc->embFontID); + break; + case fontCIDType2: + case fontCIDType2OT: + //~ should check to see if font actually uses vertical mode + fi->ff = setupEmbeddedCIDTrueTypeFont(font, &fontLoc->embFontID, + gTrue); + break; + case fontCIDType0COT: + fi->ff = setupEmbeddedOpenTypeCFFFont(font, &fontLoc->embFontID); + break; + default: + break; } - setupExternalType1Font(fontLoc->path, psName); - break; - case fontTrueType: - case fontTrueTypeOT: - psName = makePSFontName(font, font->getID()); - setupExternalTrueTypeFont(font, fontLoc->path, psName); break; - case fontCIDType2: - case fontCIDType2OT: - psName = makePSFontName(font, font->getID()); - //~ should check to see if font actually uses vertical mode - setupExternalCIDTrueTypeFont(font, fontLoc->path, psName, gTrue); + case gfxFontLocExternal: + //~ add cases for other external 16-bit fonts + switch (fontLoc->fontType) { + case fontType1: + fi->ff = setupExternalType1Font(font, fontLoc->path); + break; + case fontTrueType: + case fontTrueTypeOT: + fi->ff = setupExternalTrueTypeFont(font, fontLoc->path, + fontLoc->fontNum); + break; + case fontCIDType2: + case fontCIDType2OT: + //~ should check to see if font actually uses vertical mode + fi->ff = setupExternalCIDTrueTypeFont(font, fontLoc->path, + fontLoc->fontNum, gTrue); + break; + default: + break; + } break; - default: + case gfxFontLocResident: + if (!(fi->ff = (PSFontFileInfo *)fontFileInfo->lookup(fontLoc->path))) { + // handle psFontPassthrough + fi->ff = new PSFontFileInfo(fontLoc->path->copy(), fontLoc->fontType, + psFontFileResident); + fontFileInfo->add(fi->ff->psName, fi->ff); + } break; } - break; - case gfxFontLocResident: - psName = fontLoc->path->copy(); - break; } - if (!psName) { + if (!fi->ff) { if (font->isCIDFont()) { error(errSyntaxError, -1, - "Couldn't find a font to substitute for '{0:s}' ('{1:s}' character collection)", + "Couldn't find a font for '{0:s}' ('{1:s}' character collection)", font->getName() ? font->getName()->getCString() : "(unnamed)", ((GfxCIDFont *)font)->getCollection() ? ((GfxCIDFont *)font)->getCollection()->getCString() : "(unknown)"); - if (font16EncLen >= font16EncSize) { - font16EncSize += 16; - font16Enc = (PSFont16Enc *)greallocn(font16Enc, - font16EncSize, - sizeof(PSFont16Enc)); - } - font16Enc[font16EncLen].fontID = *font->getID(); - font16Enc[font16EncLen].enc = NULL; - ++font16EncLen; } else { error(errSyntaxError, -1, - "Couldn't find a font to substitute for '{0:s}'", + "Couldn't find a font for '{0:s}'", font->getName() ? font->getName()->getCString() : "(unnamed)"); } @@ -1843,23 +1916,14 @@ void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { if (fontLoc->locType == gfxFontLocResident && fontLoc->fontType >= fontCIDType0) { subst = gTrue; - if (font16EncLen >= font16EncSize) { - font16EncSize += 16; - font16Enc = (PSFont16Enc *)greallocn(font16Enc, - font16EncSize, - sizeof(PSFont16Enc)); - } - font16Enc[font16EncLen].fontID = *font->getID(); if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) { - font16Enc[font16EncLen].enc = fontLoc->encoding->copy(); + fi->ff->encoding = fontLoc->encoding->copy(); uMap->decRefCnt(); } else { error(errSyntaxError, -1, "Couldn't find Unicode map for 16-bit font encoding '{0:t}'", fontLoc->encoding); - font16Enc[font16EncLen].enc = NULL; } - ++font16EncLen; } delete fontLoc; @@ -1869,16 +1933,16 @@ void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { if (font->isCIDFont()) { if (level == psLevel3 || level == psLevel3Sep) { writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n", - font->getID()->num, font->getID()->gen, psName, + font->getID()->num, font->getID()->gen, fi->ff->psName, font->getWMode()); } else { writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n", - font->getID()->num, font->getID()->gen, psName, + font->getID()->num, font->getID()->gen, fi->ff->psName, font->getWMode()); } } else { writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.6g} {4:.6g}\n", - font->getID()->num, font->getID()->gen, psName, xs, ys); + font->getID()->num, font->getID()->gen, fi->ff->psName, xs, ys); for (i = 0; i < 256; i += 8) { writePS((char *)((i == 0) ? "[ " : " ")); for (j = 0; j < 8; ++j) { @@ -1903,25 +1967,29 @@ void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { } writePS("pdfMakeFont\n"); } - - delete psName; } -void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedType1Font(GfxFont *font, Ref *id) { static char hexChar[17] = "0123456789abcdef"; - Object refObj, strObj, obj1, obj2, obj3; + GString *psName; + PSFontFileInfo *ff; + Object refObj, strObj, obj1, obj2; Dict *dict; - int length1, length2, length3; + int length1, length2; int c; - int start[4]; + int start[6]; GBool binMode; - int i; + int n, i; // check if font is already embedded - if (fontNames->lookupInt(psName)) { - return; + if ((ff = (PSFontFileInfo *) + fontFileInfo->lookup(font->getEmbeddedFontName()))) { + return ff; } - fontNames->add(psName->copy(), 1); + + // generate name + // (this assumes that the PS font name matches the PDF font name) + psName = font->getEmbeddedFontName()->copy(); // get the font stream and info refObj.initRef(id->num, id->gen); @@ -1938,21 +2006,17 @@ void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) { } dict->lookup("Length1", &obj1); dict->lookup("Length2", &obj2); - dict->lookup("Length3", &obj3); - if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) { + if (!obj1.isInt() || !obj2.isInt()) { error(errSyntaxError, -1, "Missing length fields in embedded font stream dictionary"); obj1.free(); obj2.free(); - obj3.free(); goto err1; } length1 = obj1.getInt(); length2 = obj2.getInt(); - length3 = obj3.getInt(); obj1.free(); obj2.free(); - obj3.free(); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -1960,91 +2024,164 @@ void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) { embFontList->append(psName->getCString()); embFontList->append("\n"); - // copy ASCII portion of font + // check for PFB format strObj.streamReset(); - for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) { - writePSChar(c); - } + start[0] = strObj.streamGetChar(); + start[1] = strObj.streamGetChar(); + if (start[0] == 0x80 && start[1] == 0x01) { + error(errSyntaxWarning, -1, "Embedded Type 1 font is in PFB format"); + while (1) { + for (i = 2; i < 6; ++i) { + start[i] = strObj.streamGetChar(); + } + if (start[2] == EOF || start[3] == EOF || + start[4] == EOF || start[5] == EOF) { + break; + } + n = start[2] + (start[3] << 8) + (start[4] << 16) + (start[5] << 24); + if (start[1] == 0x01) { + for (i = 0; i < n; ++i) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(c); + } + } else { + for (i = 0; i < n; ++i) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(hexChar[(c >> 4) & 0x0f]); + writePSChar(hexChar[c & 0x0f]); + if (i % 32 == 31) { + writePSChar('\n'); + } + } + } + start[0] = strObj.streamGetChar(); + start[1] = strObj.streamGetChar(); + if (start[0] == EOF || start[1] == EOF || + (start[0] == 0x80 && start[1] == 0x03)) { + break; + } else if (!(start[0] == 0x80 && + (start[1] == 0x01 || start[1] == 0x02))) { + error(errSyntaxError, -1, + "Invalid PFB header in embedded font stream"); + break; + } + } + writePSChar('\n'); - // figure out if encrypted portion is binary or ASCII - binMode = gFalse; - for (i = 0; i < 4; ++i) { - start[i] = strObj.streamGetChar(); - if (start[i] == EOF) { - error(errSyntaxError, -1, - "Unexpected end of file in embedded font stream"); - goto err1; + // plain text (PFA) format + } else { + + // copy ASCII portion of font + writePSChar(start[0]); + writePSChar(start[1]); + for (i = 2; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) { + writePSChar(c); } - if (!((start[i] >= '0' && start[i] <= '9') || - (start[i] >= 'A' && start[i] <= 'F') || - (start[i] >= 'a' && start[i] <= 'f'))) - binMode = gTrue; - } - // convert binary data to ASCII - if (binMode) { + // figure out if encrypted portion is binary or ASCII + binMode = gFalse; for (i = 0; i < 4; ++i) { - writePSChar(hexChar[(start[i] >> 4) & 0x0f]); - writePSChar(hexChar[start[i] & 0x0f]); + start[i] = strObj.streamGetChar(); + if (start[i] == EOF) { + error(errSyntaxError, -1, + "Unexpected end of file in embedded font stream"); + goto err1; + } + if (!((start[i] >= '0' && start[i] <= '9') || + (start[i] >= 'A' && start[i] <= 'F') || + (start[i] >= 'a' && start[i] <= 'f'))) + binMode = gTrue; } + + // convert binary data to ASCII + if (binMode) { + for (i = 0; i < 4; ++i) { + writePSChar(hexChar[(start[i] >> 4) & 0x0f]); + writePSChar(hexChar[start[i] & 0x0f]); + } #if 0 // this causes trouble for various PostScript printers - // if Length2 is incorrect (too small), font data gets chopped, so - // we take a few extra characters from the trailer just in case - length2 += length3 >= 8 ? 8 : length3; + // if Length2 is incorrect (too small), font data gets chopped, so + // we take a few extra characters from the trailer just in case + length2 += length3 >= 8 ? 8 : length3; #endif - while (i < length2) { - if ((c = strObj.streamGetChar()) == EOF) { - break; + while (i < length2) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(hexChar[(c >> 4) & 0x0f]); + writePSChar(hexChar[c & 0x0f]); + if (++i % 32 == 0) { + writePSChar('\n'); + } } - writePSChar(hexChar[(c >> 4) & 0x0f]); - writePSChar(hexChar[c & 0x0f]); - if (++i % 32 == 0) { + if (i % 32 > 0) { writePSChar('\n'); } - } - if (i % 32 > 0) { - writePSChar('\n'); - } - // already in ASCII format -- just copy it - } else { - for (i = 0; i < 4; ++i) { - writePSChar(start[i]); - } - for (i = 4; i < length2; ++i) { - if ((c = strObj.streamGetChar()) == EOF) { - break; + // already in ASCII format -- just copy it + } else { + for (i = 0; i < 4; ++i) { + writePSChar(start[i]); + } + for (i = 4; i < length2; ++i) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(c); } - writePSChar(c); } - } - // write padding and "cleartomark" - for (i = 0; i < 8; ++i) { - writePS("00000000000000000000000000000000" - "00000000000000000000000000000000\n"); + // write padding and "cleartomark" + for (i = 0; i < 8; ++i) { + writePS("00000000000000000000000000000000" + "00000000000000000000000000000000\n"); + } + writePS("cleartomark\n"); } - writePS("cleartomark\n"); // ending comment writePS("%%EndResource\n"); + strObj.streamClose(); + strObj.free(); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; + err1: strObj.streamClose(); strObj.free(); + delete psName; + return NULL; } -//~ This doesn't handle .pfb files or binary eexec data (which only -//~ happens in pfb files?). -void PSOutputDev::setupExternalType1Font(GString *fileName, GString *psName) { +PSFontFileInfo *PSOutputDev::setupExternalType1Font(GfxFont *font, + GString *fileName) { + static char hexChar[17] = "0123456789abcdef"; + GString *psName; + PSFontFileInfo *ff; FILE *fontFile; - int c; + int buf[6]; + int c, n, i; - // check if font is already embedded - if (fontNames->lookupInt(psName)) { - return; + if (font->getName()) { + // check if font is already embedded + if ((ff = (PSFontFileInfo *)fontFileInfo->lookup(font->getName()))) { + return ff; + } + // this assumes that the PS font name matches the PDF font name + psName = font->getName()->copy(); + } else { + // generate name + //~ this won't work -- the PS font name won't match + psName = makePSFontName(font, font->getID()); } - fontNames->add(psName->copy(), 1); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2052,44 +2189,98 @@ void PSOutputDev::setupExternalType1Font(GString *fileName, GString *psName) { embFontList->append(psName->getCString()); embFontList->append("\n"); - // copy the font file + // open the font file if (!(fontFile = fopen(fileName->getCString(), "rb"))) { error(errIO, -1, "Couldn't open external font file"); - return; + return NULL; } - while ((c = fgetc(fontFile)) != EOF) { - writePSChar(c); + + // check for PFB format + buf[0] = fgetc(fontFile); + buf[1] = fgetc(fontFile); + if (buf[0] == 0x80 && buf[1] == 0x01) { + while (1) { + for (i = 2; i < 6; ++i) { + buf[i] = fgetc(fontFile); + } + if (buf[2] == EOF || buf[3] == EOF || buf[4] == EOF || buf[5] == EOF) { + break; + } + n = buf[2] + (buf[3] << 8) + (buf[4] << 16) + (buf[5] << 24); + if (buf[1] == 0x01) { + for (i = 0; i < n; ++i) { + if ((c = fgetc(fontFile)) == EOF) { + break; + } + writePSChar(c); + } + } else { + for (i = 0; i < n; ++i) { + if ((c = fgetc(fontFile)) == EOF) { + break; + } + writePSChar(hexChar[(c >> 4) & 0x0f]); + writePSChar(hexChar[c & 0x0f]); + if (i % 32 == 31) { + writePSChar('\n'); + } + } + } + buf[0] = fgetc(fontFile); + buf[1] = fgetc(fontFile); + if (buf[0] == EOF || buf[1] == EOF || + (buf[0] == 0x80 && buf[1] == 0x03)) { + break; + } else if (!(buf[0] == 0x80 && + (buf[1] == 0x01 || buf[1] == 0x02))) { + error(errSyntaxError, -1, + "Invalid PFB header in external font file"); + break; + } + } + writePSChar('\n'); + + // plain text (PFA) format + } else { + writePSChar(buf[0]); + writePSChar(buf[1]); + while ((c = fgetc(fontFile)) != EOF) { + writePSChar(c); + } } + fclose(fontFile); // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileExternal); + ff->extFileName = fileName->copy(); + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiType1C *ffT1C; - int i; + GHashIter *iter; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2109,32 +2300,35 @@ void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id, // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, + Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; - int i; + GHashIter *iter; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2144,7 +2338,7 @@ void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, // convert it to a Type 1 font if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { if (ffTT->isOpenTypeCFF()) { ffTT->convertToType1(psName->getCString(), NULL, gTrue, outputFunc, outputStream); @@ -2156,14 +2350,50 @@ void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; int *codeToGID; + GHashIter *iter; + + // get the code-to-GID mapping + if (!(fontBuf = font->readEmbFontFile(xref, &fontLen))) { + return NULL; + } + if (!(ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { + gfree(fontBuf); + return NULL; + } + codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen && + ff->codeToGIDLen == 256 && + !memcmp(ff->codeToGID, codeToGID, 256 * sizeof(int))) { + fontFileInfo->killIter(&iter); + gfree(codeToGID); + delete ffTT; + gfree(fontBuf); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2172,38 +2402,57 @@ void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, embFontList->append("\n"); // convert it to a Type 42 font - if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { - codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); - ffTT->convertToType42(psName->getCString(), - ((Gfx8BitFont *)font)->getHasEncoding() - ? ((Gfx8BitFont *)font)->getEncoding() - : (char **)NULL, - codeToGID, outputFunc, outputStream); - if (codeToGID) { - if (font8InfoLen >= font8InfoSize) { - font8InfoSize += 16; - font8Info = (PSFont8Info *)greallocn(font8Info, - font8InfoSize, - sizeof(PSFont8Info)); - } - font8Info[font8InfoLen].fontID = *font->getID(); - font8Info[font8InfoLen].codeToGID = codeToGID; - ++font8InfoLen; - } - delete ffTT; - } - gfree(fontBuf); - } + ffTT->convertToType42(psName->getCString(), + ((Gfx8BitFont *)font)->getHasEncoding() + ? ((Gfx8BitFont *)font)->getEncoding() + : (char **)NULL, + codeToGID, outputFunc, outputStream); + delete ffTT; + gfree(fontBuf); // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + ff->codeToGID = codeToGID; + ff->codeToGIDLen = 256; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, GString *fileName, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, + GString *fileName, + int fontNum) { + GString *psName; + PSFontFileInfo *ff; FoFiTrueType *ffTT; int *codeToGID; + GHashIter *iter; + + // get the code-to-GID mapping + if (!(ffTT = FoFiTrueType::load(fileName->getCString(), fontNum))) { + return NULL; + } + codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileExternal && + ff->type == font->getType() && + !ff->extFileName->cmp(fileName) && + ff->codeToGIDLen == 256 && + !memcmp(ff->codeToGID, codeToGID, 256 * sizeof(int))) { + fontFileInfo->killIter(&iter); + gfree(codeToGID); + delete ffTT; + return ff; + } + } + + // generate name + psName = makePSFontName(font, font->getID()); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2212,55 +2461,45 @@ void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, GString *fileName, embFontList->append("\n"); // convert it to a Type 42 font - if ((ffTT = FoFiTrueType::load(fileName->getCString()))) { - codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); - ffTT->convertToType42(psName->getCString(), - ((Gfx8BitFont *)font)->getHasEncoding() - ? ((Gfx8BitFont *)font)->getEncoding() - : (char **)NULL, - codeToGID, outputFunc, outputStream); - if (codeToGID) { - if (font8InfoLen >= font8InfoSize) { - font8InfoSize += 16; - font8Info = (PSFont8Info *)greallocn(font8Info, - font8InfoSize, - sizeof(PSFont8Info)); - } - font8Info[font8InfoLen].fontID = *font->getID(); - font8Info[font8InfoLen].codeToGID = codeToGID; - ++font8InfoLen; - } - delete ffTT; - } + ffTT->convertToType42(psName->getCString(), + ((Gfx8BitFont *)font)->getHasEncoding() + ? ((Gfx8BitFont *)font)->getEncoding() + : (char **)NULL, + codeToGID, outputFunc, outputStream); + delete ffTT; // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileExternal); + ff->extFileName = fileName->copy(); + ff->codeToGID = codeToGID; + ff->codeToGIDLen = 256; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiType1C *ffT1C; - int i; + GHashIter *iter; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2287,14 +2526,46 @@ void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, - GString *psName, - GBool needVerticalMetrics) { +PSFontFileInfo *PSOutputDev::setupEmbeddedCIDTrueTypeFont( + GfxFont *font, Ref *id, + GBool needVerticalMetrics) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; + int *codeToGID; + int codeToGIDLen; + GHashIter *iter; + + // get the code-to-GID mapping + codeToGID = ((GfxCIDFont *)font)->getCIDToGID(); + codeToGIDLen = ((GfxCIDFont *)font)->getCIDToGIDLen(); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen && + ff->codeToGIDLen == codeToGIDLen && + ((!ff->codeToGID && !codeToGID) || + (ff->codeToGID && codeToGID && + !memcmp(ff->codeToGID, codeToGID, codeToGIDLen * sizeof(int))))) { + fontFileInfo->killIter(&iter); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2304,19 +2575,17 @@ void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, // convert it to a Type 0 font if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { if (globalParams->getPSLevel() >= psLevel3) { // Level 3: use a CID font ffTT->convertToCIDType2(psName->getCString(), - ((GfxCIDFont *)font)->getCIDToGID(), - ((GfxCIDFont *)font)->getCIDToGIDLen(), + codeToGID, codeToGIDLen, needVerticalMetrics, outputFunc, outputStream); } else { // otherwise: use a non-CID composite font ffTT->convertToType0(psName->getCString(), - ((GfxCIDFont *)font)->getCIDToGID(), - ((GfxCIDFont *)font)->getCIDToGIDLen(), + codeToGID, codeToGIDLen, needVerticalMetrics, outputFunc, outputStream); } @@ -2327,18 +2596,104 @@ void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + if (codeToGIDLen) { + ff->codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); + memcpy(ff->codeToGID, codeToGID, codeToGIDLen * sizeof(int)); + ff->codeToGIDLen = codeToGIDLen; + } + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font, - GString *fileName, - GString *psName, - GBool needVerticalMetrics) { +PSFontFileInfo *PSOutputDev::setupExternalCIDTrueTypeFont( + GfxFont *font, + GString *fileName, + int fontNum, + GBool needVerticalMetrics) { + GString *psName; + PSFontFileInfo *ff; FoFiTrueType *ffTT; int *codeToGID; int codeToGIDLen; CharCodeToUnicode *ctu; Unicode uBuf[8]; int cmap, code; + GHashIter *iter; + + // create a code-to-GID mapping, via Unicode + if (!(ffTT = FoFiTrueType::load(fileName->getCString(), fontNum))) { + return NULL; + } + if (!(ctu = ((GfxCIDFont *)font)->getToUnicode())) { + error(errSyntaxError, -1, + "Couldn't find a mapping to Unicode for font '{0:s}'", + font->getName() ? font->getName()->getCString() : "(unnamed)"); + delete ffTT; + return NULL; + } + // look for a Unicode cmap + for (cmap = 0; cmap < ffTT->getNumCmaps(); ++cmap) { + if ((ffTT->getCmapPlatform(cmap) == 3 && + ffTT->getCmapEncoding(cmap) == 1) || + ffTT->getCmapPlatform(cmap) == 0) { + break; + } + } + if (cmap >= ffTT->getNumCmaps()) { + error(errSyntaxError, -1, + "Couldn't find a Unicode cmap in font '{0:s}'", + font->getName() ? font->getName()->getCString() : "(unnamed)"); + ctu->decRefCnt(); + delete ffTT; + return NULL; + } + // map CID -> Unicode -> GID + if (ctu->isIdentity()) { + codeToGIDLen = 65536; + } else { + codeToGIDLen = ctu->getLength(); + } + codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); + for (code = 0; code < codeToGIDLen; ++code) { + if (ctu->mapToUnicode(code, uBuf, 8) > 0) { + codeToGID[code] = ffTT->mapCodeToGID(cmap, uBuf[0]); + } else { + codeToGID[code] = 0; + } + } + ctu->decRefCnt(); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileExternal && + ff->type == font->getType() && + !ff->extFileName->cmp(fileName) && + ff->codeToGIDLen == codeToGIDLen && + ff->codeToGID && + !memcmp(ff->codeToGID, codeToGID, codeToGIDLen * sizeof(int))) { + fontFileInfo->killIter(&iter); + gfree(codeToGID); + delete ffTT; + return ff; + } + } + + // check for embedding permission + if (ffTT->getEmbeddingRights() < 1) { + error(errSyntaxError, -1, + "TrueType font '{0:s}' does not allow embedding", + font->getName() ? font->getName()->getCString() : "(unnamed)"); + gfree(codeToGID); + delete ffTT; + return NULL; + } + + // generate name + psName = makePSFontName(font, font->getID()); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2348,90 +2703,55 @@ void PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font, // convert it to a Type 0 font //~ this should use fontNum to load the correct font - if ((ffTT = FoFiTrueType::load(fileName->getCString()))) { - - // check for embedding permission - if (ffTT->getEmbeddingRights() >= 1) { - - // create a CID-to-GID mapping, via Unicode - if ((ctu = ((GfxCIDFont *)font)->getToUnicode())) { - // look for a Unicode cmap - for (cmap = 0; cmap < ffTT->getNumCmaps(); ++cmap) { - if ((ffTT->getCmapPlatform(cmap) == 3 && - ffTT->getCmapEncoding(cmap) == 1) || - ffTT->getCmapPlatform(cmap) == 0) { - break; - } - } - if (cmap < ffTT->getNumCmaps()) { - // map CID -> Unicode -> GID - codeToGIDLen = ctu->getLength(); - codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); - for (code = 0; code < codeToGIDLen; ++code) { - if (ctu->mapToUnicode(code, uBuf, 8) > 0) { - codeToGID[code] = ffTT->mapCodeToGID(cmap, uBuf[0]); - } else { - codeToGID[code] = 0; - } - } - if (globalParams->getPSLevel() >= psLevel3) { - // Level 3: use a CID font - ffTT->convertToCIDType2(psName->getCString(), - codeToGID, codeToGIDLen, - needVerticalMetrics, - outputFunc, outputStream); - } else { - // otherwise: use a non-CID composite font - ffTT->convertToType0(psName->getCString(), - codeToGID, codeToGIDLen, - needVerticalMetrics, - outputFunc, outputStream); - } - gfree(codeToGID); - } - ctu->decRefCnt(); - } else { - error(errSyntaxError, -1, - "Couldn't find a mapping to Unicode for font '{0:s}'", - font->getName() ? font->getName()->getCString() : "(unnamed)"); - } - } else { - error(errSyntaxError, -1, - "TrueType font '%s' does not allow embedding", - font->getName() ? font->getName()->getCString() : "(unnamed)"); - - } - delete ffTT; + if (globalParams->getPSLevel() >= psLevel3) { + // Level 3: use a CID font + ffTT->convertToCIDType2(psName->getCString(), + codeToGID, codeToGIDLen, + needVerticalMetrics, + outputFunc, outputStream); + } else { + // otherwise: use a non-CID composite font + ffTT->convertToType0(psName->getCString(), + codeToGID, codeToGIDLen, + needVerticalMetrics, + outputFunc, outputStream); } + delete ffTT; // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileExternal); + ff->extFileName = fileName->copy(); + ff->codeToGID = codeToGID; + ff->codeToGIDLen = codeToGIDLen; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, + Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; - int i; + GHashIter *iter; + int n; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2441,7 +2761,7 @@ void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, // convert it to a Type 0 font if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { if (ffTT->isOpenTypeCFF()) { if (globalParams->getPSLevel() >= psLevel3) { // Level 3: use a CID font @@ -2464,10 +2784,22 @@ void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + if ((n = ((GfxCIDFont *)font)->getCIDToGIDLen())) { + ff->codeToGID = (int *)gmallocn(n, sizeof(int)); + memcpy(ff->codeToGID, ((GfxCIDFont *)font)->getCIDToGID(), n * sizeof(int)); + ff->codeToGIDLen = n; + } + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupType3Font(GfxFont *font, GString *psName, - Dict *parentResDict) { +PSFontFileInfo *PSOutputDev::setupType3Font(GfxFont *font, + Dict *parentResDict) { + PSFontFileInfo *ff; + GString *psName; Dict *resDict; Dict *charProcs; Object charProc; @@ -2477,6 +2809,10 @@ void PSOutputDev::setupType3Font(GfxFont *font, GString *psName, GString *buf; int i; + // generate name + psName = GString::format("T3_{0:d}_{1:d}", + font->getID()->num, font->getID()->gen); + // set up resources used by font if ((resDict = ((Gfx8BitFont *)font)->getResources())) { inType3Char = gTrue; @@ -2528,7 +2864,7 @@ void PSOutputDev::setupType3Font(GfxFont *font, GString *psName, writePS("/"); writePSName(charProcs->getKey(i)); writePS(" {\n"); - gfx->display(charProcs->getVal(i, &charProc)); + gfx->display(charProcs->getValNF(i, &charProc)); charProc.free(); if (t3String) { if (t3Cacheable) { @@ -2558,6 +2894,10 @@ void PSOutputDev::setupType3Font(GfxFont *font, GString *psName, // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + fontFileInfo->add(ff->psName, ff); + return ff; } // Make a unique PS font name, based on the names given in the PDF @@ -2567,16 +2907,14 @@ GString *PSOutputDev::makePSFontName(GfxFont *font, Ref *id) { if ((s = font->getEmbeddedFontName())) { psName = filterPSName(s); - if (!fontNames->lookupInt(psName)) { - fontNames->add(psName->copy(), 1); + if (!fontFileInfo->lookup(psName)) { return psName; } delete psName; } if ((s = font->getName())) { psName = filterPSName(s); - if (!fontNames->lookupInt(psName)) { - fontNames->add(psName->copy(), 1); + if (!fontFileInfo->lookup(psName)) { return psName; } delete psName; @@ -2591,7 +2929,6 @@ GString *PSOutputDev::makePSFontName(GfxFont *font, Ref *id) { psName->append('_')->append(s); delete s; } - fontNames->add(psName->copy(), 1); return psName; } @@ -2651,7 +2988,7 @@ void PSOutputDev::setupImages(Dict *resDict) { } void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask) { - GBool useRLE, useCompressed, useASCIIHex; + GBool useLZW, useRLE, useCompressed, useASCIIHex; GString *s; int c; int size, line, col, i; @@ -2660,21 +2997,27 @@ void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask) { //~ this does not correctly handle the DeviceN color space //~ -- need to use DeviceNRecoder if (level < psLevel2) { - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; useASCIIHex = gTrue; } else { if (globalParams->getPSUncompressPreloadedImages()) { - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; } else { s = str->getPSFilter(level < psLevel3 ? 2 : 3, ""); if (s) { - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gTrue; delete s; } else { - useRLE = gTrue; + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } useCompressed = gFalse; } } @@ -2683,7 +3026,9 @@ void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask) { if (useCompressed) { str = str->getUndecodedStream(); } - if (useRLE) { + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { str = new RunLengthEncoder(str); } if (useASCIIHex) { @@ -2722,9 +3067,9 @@ void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask) { } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); // add one entry for the final line of data; add another entry - // because the RunLengthDecode filter may read past the end + // because the LZWDecode/RunLengthDecode filter may read past the end ++size; - if (useRLE) { + if (useLZW || useRLE) { ++size; } writePSFmt("{0:d} array dup /{1:s}Data_{2:d}_{3:d} exch def\n", @@ -2771,7 +3116,7 @@ void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask) { } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n")); - if (useRLE) { + if (useLZW || useRLE) { ++line; writePSFmt("{0:d} <> put\n", line); } else { @@ -2799,7 +3144,7 @@ void PSOutputDev::setupForms(Dict *resDict) { xObj.streamGetDict()->lookup("Subtype", &subtypeObj); if (subtypeObj.isName("Form")) { if (xObjRef.isRef()) { - setupForm(xObjRef.getRef(), &xObj); + setupForm(&xObjRef, &xObj); } else { error(errSyntaxError, -1, "Form in resource dict is not an indirect reference"); @@ -2814,7 +3159,7 @@ void PSOutputDev::setupForms(Dict *resDict) { xObjDict.free(); } -void PSOutputDev::setupForm(Ref id, Object *strObj) { +void PSOutputDev::setupForm(Object *strRef, Object *strObj) { Dict *dict, *resDict; Object matrixObj, bboxObj, resObj, obj1; double m[6], bbox[4]; @@ -2824,7 +3169,8 @@ void PSOutputDev::setupForm(Ref id, Object *strObj) { // check if form is already defined for (i = 0; i < formIDLen; ++i) { - if (formIDs[i].num == id.num && formIDs[i].gen == id.gen) { + if (formIDs[i].num == strRef->getRefNum() && + formIDs[i].gen == strRef->getRefGen()) { return; } } @@ -2838,7 +3184,7 @@ void PSOutputDev::setupForm(Ref id, Object *strObj) { } formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref)); } - formIDs[formIDLen++] = id; + formIDs[formIDLen++] = strRef->getRef(); dict = strObj->streamGetDict(); @@ -2875,7 +3221,7 @@ void PSOutputDev::setupForm(Ref id, Object *strObj) { dict->lookup("Resources", &resObj); resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; - writePSFmt("/f_{0:d}_{1:d} {{\n", id.num, id.gen); + writePSFmt("/f_{0:d}_{1:d} {{\n", strRef->getRefNum(), strRef->getRefGen()); writePS("q\n"); writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", m[0], m[1], m[2], m[3], m[4], m[5]); @@ -2885,7 +3231,7 @@ void PSOutputDev::setupForm(Ref id, Object *strObj) { box.x2 = bbox[2]; box.y2 = bbox[3]; gfx = new Gfx(doc, this, resDict, &box, &box); - gfx->display(strObj); + gfx->display(strRef); delete gfx; writePS("Q\n"); @@ -2905,6 +3251,7 @@ GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, GBool rasterize; #if HAVE_SPLASH GBool mono; + GBool useLZW; double dpi; SplashOutputDev *splashOut; SplashColor paperColor; @@ -2915,10 +3262,11 @@ GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, Object obj; Guchar *p; Guchar col[4]; + char buf[4096]; double hDPI2, vDPI2; double m0, m1, m2, m3, m4, m5; int nStripes, stripeH, stripeY; - int c, w, h, x, y, comp, i; + int w, h, x, y, comp, i, n; #endif if (globalParams->getPSAlwaysRasterize()) { @@ -2939,6 +3287,7 @@ GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, // get the rasterization parameters dpi = globalParams->getPSRasterResolution(); mono = globalParams->getPSRasterMono(); + useLZW = globalParams->getPSLZW(); // start the PS page page->makeBox(dpi, dpi, rotateA, useMediaBox, gFalse, @@ -2987,8 +3336,8 @@ GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, sliceW = (int)((box.x2 - box.x1) * hDPI2 / 72.0); sliceH = (int)((box.y2 - box.y1) * vDPI2 / 72.0); } - nStripes = (int)ceil((double)(sliceW * sliceH) / - (double)rasterizationSliceSize); + nStripes = (int)ceil(((double)sliceW * (double)sliceH) / + (double)globalParams->getPSRasterSliceSize()); stripeH = (sliceH + nStripes - 1) / nStripes; // render the stripes @@ -3094,21 +3443,29 @@ GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, } else { writePS(" /ASCII85Decode filter\n"); } - writePS(" /RunLengthDecode filter\n"); + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else { + writePS(" /RunLengthDecode filter\n"); + } writePS(">>\n"); writePS("image\n"); obj.initNull(); p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); str0 = new MemStream((char *)p, 0, w * h * (mono ? 1 : 3), &obj); - str = new RunLengthEncoder(str0); + if (useLZW) { + str = new LZWEncoder(str0); + } else { + str = new RunLengthEncoder(str0); + } if (globalParams->getPSASCIIHex()) { str = new ASCIIHexEncoder(str); } else { str = new ASCII85Encoder(str); } str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); delete str; @@ -3148,8 +3505,13 @@ void PSOutputDev::startPage(int pageNum, GfxState *state) { if (paperMatch) { page = doc->getCatalog()->getPage(pageNum); imgLLX = imgLLY = 0; - imgURX = (int)ceil(page->getMediaWidth()); - imgURY = (int)ceil(page->getMediaHeight()); + if (globalParams->getPSUseCropBoxAsPage()) { + imgURX = (int)ceil(page->getCropWidth()); + imgURY = (int)ceil(page->getCropHeight()); + } else { + imgURX = (int)ceil(page->getMediaWidth()); + imgURY = (int)ceil(page->getMediaHeight()); + } if (state->getRotate() == 90 || state->getRotate() == 270) { t = imgURX; imgURX = imgURY; @@ -3160,6 +3522,9 @@ void PSOutputDev::startPage(int pageNum, GfxState *state) { } writePS("%%BeginPageSetup\n"); } + if (mode != psModeForm) { + writePS("xpdf begin\n"); + } // underlays if (underlayCbk) { @@ -3356,6 +3721,7 @@ void PSOutputDev::endPage() { } writePS("%%PageTrailer\n"); writePageTrailer(); + writePS("end\n"); } } @@ -3371,8 +3737,13 @@ void PSOutputDev::restoreState(GfxState *state) { void PSOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) { - writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", - m11, m12, m21, m22, m31, m32); + if (fabs(m11 * m22 - m12 * m21) < 0.00001) { + // avoid a singular (or close-to-singular) matrix + writePSFmt("[0.00001 0 0 0.00001 {0:.6g} {1:.6g}] Tm\n", m31, m32); + } else { + writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", + m11, m12, m21, m22, m31, m32); + } } void PSOutputDev::updateLineDash(GfxState *state) { @@ -3737,7 +4108,7 @@ void PSOutputDev::eoFill(GfxState *state) { writePS("f*\n"); } -void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, +void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -3770,6 +4141,7 @@ void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, box.x2 = bbox[2]; box.y2 = bbox[3]; gfx2 = new Gfx(doc, this, resDict, &box, NULL); + gfx2->takeContentStreamStack(gfx); writePS("/x {\n"); if (paintType == 2) { writePSFmt("{0:.6g} 0 {1:.6g} {2:.6g} {3:.6g} {4:.6g} setcachedevice\n", @@ -3785,7 +4157,7 @@ void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, } inType3Char = gTrue; ++numTilingPatterns; - gfx2->display(str); + gfx2->display(strRef); --numTilingPatterns; inType3Char = gFalse; writePS("} def\n"); @@ -3944,7 +4316,7 @@ GBool PSOutputDev::radialShadedFill(GfxState *state, double xMin, yMin, xMax, yMax; double x0, y0, r0, x1, y1, r1, t0, t1; double xa, ya, ra; - double sz, sMin, sMax, h, ta; + double sMin, sMax, h, ta; double sLeft, sRight, sTop, sBottom, sZero, sDiag; GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; GBool haveSMin, haveSMax; @@ -3970,18 +4342,14 @@ GBool PSOutputDev::radialShadedFill(GfxState *state, if (h == 0) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else if (r1 - r0 == 0) { enclosed = gFalse; theta = 0; - sz = 0; // make gcc happy } else if (fabs(r1 - r0) >= h) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else { enclosed = gFalse; - sz = -r0 / (r1 - r0); theta = asin((r1 - r0) / h); } if (enclosed) { @@ -4267,8 +4635,9 @@ void PSOutputDev::drawString(GfxState *state, GString *s) { int wMode; int *codeToGID; GString *s2; - double dx, dy, originX, originY; + double dx, dy, originX, originY, originX0, originY0, tOriginX0, tOriginY0; char *p; + PSFontInfo *fi; UnicodeMap *uMap; CharCode code; Unicode u[8]; @@ -4292,30 +4661,32 @@ void PSOutputDev::drawString(GfxState *state, GString *s) { } wMode = font->getWMode(); + fi = NULL; + for (i = 0; i < fontInfo->getLength(); ++i) { + fi = (PSFontInfo *)fontInfo->get(i); + if (fi->fontID.num == font->getID()->num && + fi->fontID.gen == font->getID()->gen) { + break; + } + fi = NULL; + } + // check for a subtitute 16-bit font uMap = NULL; codeToGID = NULL; if (font->isCIDFont()) { - for (i = 0; i < font16EncLen; ++i) { - if (font->getID()->num == font16Enc[i].fontID.num && - font->getID()->gen == font16Enc[i].fontID.gen) { - if (!font16Enc[i].enc) { - // font substitution failed, so don't output any text - return; - } - uMap = globalParams->getUnicodeMap(font16Enc[i].enc); - break; - } + if (!(fi && fi->ff)) { + // font substitution failed, so don't output any text + return; + } + if (fi->ff->encoding) { + uMap = globalParams->getUnicodeMap(fi->ff->encoding); } - // check for a code-to-GID map + // check for an 8-bit code-to-GID map } else { - for (i = 0; i < font8InfoLen; ++i) { - if (font->getID()->num == font8Info[i].fontID.num && - font->getID()->gen == font8Info[i].fontID.gen) { - codeToGID = font8Info[i].codeToGID; - break; - } + if (fi && fi->ff) { + codeToGID = fi->ff->codeToGID; } } @@ -4326,10 +4697,18 @@ void PSOutputDev::drawString(GfxState *state, GString *s) { s2 = new GString(); dxdySize = font->isCIDFont() ? 8 : s->getLength(); dxdy = (double *)gmallocn(2 * dxdySize, sizeof(double)); + originX0 = originY0 = 0; // make gcc happy while (len > 0) { n = font->getNextChar(p, len, &code, u, (int)(sizeof(u) / sizeof(Unicode)), &uLen, &dx, &dy, &originX, &originY); + //~ this doesn't handle the case where the origin offset changes + //~ within a string of characters -- which could be fixed by + //~ modifying dx,dy as needed for each character + if (p == s->getCString()) { + originX0 = originX; + originY0 = originY; + } dx *= state->getFontSize(); dy *= state->getFontSize(); if (wMode) { @@ -4389,8 +4768,14 @@ void PSOutputDev::drawString(GfxState *state, GString *s) { if (uMap) { uMap->decRefCnt(); } + originX0 *= state->getFontSize(); + originY0 *= state->getFontSize(); + state->textTransformDelta(originX0, originY0, &tOriginX0, &tOriginY0); if (nChars > 0) { + if (wMode) { + writePSFmt("{0:.6g} {1:.6g} rmoveto\n", -tOriginX0, -tOriginY0); + } writePSString(s2); writePS("\n["); for (i = 0; i < 2 * nChars; ++i) { @@ -4400,6 +4785,9 @@ void PSOutputDev::drawString(GfxState *state, GString *s) { writePSFmt("{0:.6g}", dxdy[i]); } writePS("] Tj\n"); + if (wMode) { + writePSFmt("{0:.6g} {1:.6g} rmoveto\n", tOriginX0, tOriginY0); + } } gfree(dxdy); delete s2; @@ -4418,7 +4806,7 @@ void PSOutputDev::endTextObject(GfxState *state) { void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, GBool interpolate) { int len; len = height * ((width + 7) / 8); @@ -4442,7 +4830,8 @@ void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { int len; len = height * ((width * colorMap->getNumPixelComps() * @@ -4474,7 +4863,7 @@ void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert) { + GBool maskInvert, GBool interpolate) { int len; len = height * ((width * colorMap->getNumPixelComps() * @@ -4684,10 +5073,11 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, GBool emitRect, addRect, extendRect; GString *s; int n, numComps; - GBool useRLE, useASCII, useASCIIHex, useCompressed; + GBool useLZW, useRLE, useASCII, useASCIIHex, useCompressed; GfxSeparationColorSpace *sepCS; GfxColor color; GfxCMYK cmyk; + char buf[4096]; int c; int col, i, j, x0, x1, y, maskXor; @@ -4821,14 +5211,14 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1; ++rectsOutLen; } - writePSFmt("{0:d} array 0\n", rectsOutLen * 4); + writePSFmt("{0:d} {1:d}\n", maskWidth, maskHeight); for (i = 0; i < rectsOutLen; ++i) { - writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n", + writePSFmt("{0:d} {1:d} {2:d} {3:d} pr\n", rectsOut[i].x0, rectsOut[i].y0, rectsOut[i].x1 - rectsOut[i].x0, rectsOut[i].y1 - rectsOut[i].y0); } - writePSFmt("pop {0:d} {1:d} pdfImClip\n", width, height); + writePS("pop pop pdfImClip\n"); gfree(rectsOut); gfree(rects0); gfree(rects1); @@ -4923,14 +5313,14 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1; ++rectsOutLen; } - writePSFmt("{0:d} array 0\n", rectsOutLen * 4); + writePSFmt("{0:d} {1:d}\n", maskWidth, maskHeight); for (i = 0; i < rectsOutLen; ++i) { - writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n", + writePSFmt("{0:d} {1:d} {2:d} {3:d} pr\n", rectsOut[i].x0, rectsOut[i].y0, rectsOut[i].x1 - rectsOut[i].x0, rectsOut[i].y1 - rectsOut[i].y0); } - writePSFmt("pop {0:d} {1:d} pdfImClip\n", maskWidth, maskHeight); + writePS("pop pop pdfImClip\n"); gfree(rectsOut); gfree(rects0); gfree(rects1); @@ -4951,7 +5341,11 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, if (inlineImg) { // create an array str2 = new FixedLengthEncoder(str, len); - str2 = new RunLengthEncoder(str2); + if (globalParams->getPSLZW()) { + str2 = new LZWEncoder(str2); + } else { + str2 = new RunLengthEncoder(str2); + } if (useASCIIHex) { str2 = new ASCIIHexEncoder(str2); } else { @@ -4994,8 +5388,8 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); writePS((char *)(useASCIIHex ? ">\n" : "~>\n")); - // add an extra entry because the RunLengthDecode filter may - // read past the end + // add an extra entry because the LZWDecode/RunLengthDecode + // filter may read past the end writePS("<>]\n"); writePS("0\n"); str2->close(); @@ -5065,7 +5459,7 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, if ((mode == psModeForm || inType3Char || preload) && globalParams->getPSUncompressPreloadedImages()) { s = NULL; - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; useASCII = gFalse; } else { @@ -5073,11 +5467,17 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, " "); if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) || inlineImg || !s) { - useRLE = gTrue; + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } useASCII = !(mode == psModeForm || inType3Char || preload); useCompressed = gFalse; } else { - useRLE = gFalse; + useLZW = useRLE = gFalse; useASCII = str->isBinary() && !(mode == psModeForm || inType3Char || preload); useCompressed = gTrue; @@ -5087,7 +5487,9 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, writePSFmt(" /ASCII{0:s}Decode filter\n", useASCIIHex ? "Hex" : "85"); } - if (useRLE) { + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else if (useRLE) { writePS(" /RunLengthDecode filter\n"); } if (useCompressed) { @@ -5119,8 +5521,10 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, str = new DeviceNRecoder(str, width, height, colorMap); } - // add RunLengthEncode and ASCIIHex/85 encode filters - if (useRLE) { + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { str = new RunLengthEncoder(str); } if (useASCII) { @@ -5141,12 +5545,14 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, n = 0; } else { // need to read the stream to count characters -- the length - // is data-dependent (because of ASCII and RLE filters) + // is data-dependent (because of ASCII and LZW/RunLength + // filters) str->reset(); n = 0; - while ((c = str->getChar()) != EOF) { - ++n; - } + do { + i = str->discardChars(4096); + n += i; + } while (i == 4096); str->close(); } // +6/7 for "pdfIm\n" / "pdfImM\n" @@ -5170,8 +5576,8 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, // copy the stream data str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); @@ -5185,7 +5591,7 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap, #endif // delete encoders - if (useRLE || useASCII || inlineImg) { + if (useLZW || useRLE || useASCII || inlineImg) { delete str; } } @@ -5204,18 +5610,20 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, Stream *str2; GString *s; int n, numComps; - GBool useRLE, useASCII, useASCIIHex, useCompressed; - GBool maskUseRLE, maskUseASCII, maskUseCompressed; + GBool useLZW, useRLE, useASCII, useASCIIHex, useCompressed; + GBool maskUseLZW, maskUseRLE, maskUseASCII, maskUseCompressed; GString *maskFilters; GfxSeparationColorSpace *sepCS; GfxColor color; GfxCMYK cmyk; + char buf[4096]; int c; int col, i; useASCIIHex = globalParams->getPSASCIIHex(); - useRLE = useASCII = useCompressed = gFalse; // make gcc happy - maskUseRLE = maskUseASCII = maskUseCompressed = gFalse; // make gcc happy + useLZW = useRLE = useASCII = useCompressed = gFalse; // make gcc happy + maskUseLZW = maskUseRLE = maskUseASCII = gFalse; // make gcc happy + maskUseCompressed = gFalse; // make gcc happy maskFilters = NULL; // make gcc happy // explicit masking @@ -5225,17 +5633,23 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, if ((mode == psModeForm || inType3Char || preload) && globalParams->getPSUncompressPreloadedImages()) { s = NULL; - maskUseRLE = gFalse; + maskUseLZW = maskUseRLE = gFalse; maskUseCompressed = gFalse; maskUseASCII = gFalse; } else { s = maskStr->getPSFilter(3, " "); if (!s) { - maskUseRLE = gTrue; + if (globalParams->getPSLZW()) { + maskUseLZW = gTrue; + maskUseRLE = gFalse; + } else { + maskUseRLE = gTrue; + maskUseLZW = gFalse; + } maskUseASCII = !(mode == psModeForm || inType3Char || preload); maskUseCompressed = gFalse; } else { - maskUseRLE = gFalse; + maskUseLZW = maskUseRLE = gFalse; maskUseASCII = maskStr->isBinary() && !(mode == psModeForm || inType3Char || preload); maskUseCompressed = gTrue; @@ -5246,7 +5660,9 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, maskFilters->appendf(" /ASCII{0:s}Decode filter\n", useASCIIHex ? "Hex" : "85"); } - if (maskUseRLE) { + if (maskUseLZW) { + maskFilters->append(" /LZWDecode filter\n"); + } else if (maskUseRLE) { maskFilters->append(" /RunLengthDecode filter\n"); } if (maskUseCompressed) { @@ -5263,11 +5679,13 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, writePS(maskFilters->getCString()); writePS("pdfMask\n"); - // add RunLengthEncode and ASCIIHex/85 encode filters + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters if (maskUseCompressed) { maskStr = maskStr->getUndecodedStream(); } - if (maskUseRLE) { + if (maskUseLZW) { + maskStr = new LZWEncoder(maskStr); + } else if (maskUseRLE) { maskStr = new RunLengthEncoder(maskStr); } if (maskUseASCII) { @@ -5280,15 +5698,15 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, // copy the stream data maskStr->reset(); - while ((c = maskStr->getChar()) != EOF) { - writePSChar(c); + while ((n = maskStr->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } maskStr->close(); writePSChar('\n'); writePS("%-EOD-\n"); // delete encoders - if (maskUseRLE || maskUseASCII) { + if (maskUseLZW || maskUseRLE || maskUseASCII) { delete maskStr; } } @@ -5305,7 +5723,11 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, if (inlineImg) { // create an array str2 = new FixedLengthEncoder(str, len); - str2 = new RunLengthEncoder(str2); + if (globalParams->getPSLZW()) { + str2 = new LZWEncoder(str2); + } else { + str2 = new RunLengthEncoder(str2); + } if (useASCIIHex) { str2 = new ASCIIHexEncoder(str2); } else { @@ -5348,8 +5770,8 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); writePS((char *)(useASCIIHex ? ">\n" : "~>\n")); - // add an extra entry because the RunLengthDecode filter may - // read past the end + // add an extra entry because the LZWDecode/RunLengthDecode + // filter may read past the end writePS("<>]\n"); writePS("0\n"); str2->close(); @@ -5436,7 +5858,7 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, if ((mode == psModeForm || inType3Char || preload) && globalParams->getPSUncompressPreloadedImages()) { s = NULL; - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; useASCII = gFalse; } else { @@ -5444,11 +5866,17 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, " "); if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) || inlineImg || !s) { - useRLE = gTrue; + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } useASCII = !(mode == psModeForm || inType3Char || preload); useCompressed = gFalse; } else { - useRLE = gFalse; + useLZW = useRLE = gFalse; useASCII = str->isBinary() && !(mode == psModeForm || inType3Char || preload); useCompressed = gTrue; @@ -5458,7 +5886,9 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, writePSFmt(" /ASCII{0:s}Decode filter\n", useASCIIHex ? "Hex" : "85"); } - if (useRLE) { + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else if (useRLE) { writePS(" /RunLengthDecode filter\n"); } if (useCompressed) { @@ -5538,8 +5968,10 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, str = new DeviceNRecoder(str, width, height, colorMap); } - // add RunLengthEncode and ASCIIHex/85 encode filters - if (useRLE) { + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { str = new RunLengthEncoder(str); } if (useASCII) { @@ -5552,8 +5984,8 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, // copy the stream data str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); @@ -5562,7 +5994,7 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap, writePS("%-EOD-\n"); // delete encoders - if (useRLE || useASCII || inlineImg) { + if (useLZW || useRLE || useASCII || inlineImg) { delete str; } } @@ -6267,6 +6699,10 @@ void PSOutputDev::type3D0(GfxState *state, double wx, double wy) { void PSOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { + if (t3String) { + error(errSyntaxError, -1, "Multiple 'd1' operators in Type 3 CharProc"); + return; + } t3WX = wx; t3WY = wy; t3LLX = llx; @@ -6286,7 +6722,8 @@ void PSOutputDev::drawForm(Ref id) { void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) { Stream *str; - int c; + char buf[4096]; + int n; if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) { str = level1Stream; @@ -6294,8 +6731,8 @@ void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) { str = psStream; } str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); } @@ -6462,6 +6899,14 @@ void PSOutputDev::writePSChar(char c) { } } +void PSOutputDev::writePSBlock(char *s, int len) { + if (t3String) { + t3String->append(s, len); + } else { + (*outputFunc)(outputStream, s, len); + } +} + void PSOutputDev::writePS(const char *s) { if (t3String) { t3String->append(s); @@ -6564,7 +7009,9 @@ GString *PSOutputDev::filterPSName(GString *name) { // Write a DSC-compliant <textline>. void PSOutputDev::writePSTextLine(GString *s) { - int i, j, step; + TextString *ts; + Unicode *u; + int i, j; int c; // - DSC comments must be printable ASCII; control chars and @@ -6574,17 +7021,10 @@ void PSOutputDev::writePSTextLine(GString *s) { // for the keyword, which was emitted by the caller) // - lines that start with a left paren are treated as <text> // instead of <textline>, so we escape a leading paren - if (s->getLength() >= 2 && - (s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - i = 3; - step = 2; - } else { - i = 0; - step = 1; - } - for (j = 0; i < s->getLength() && j < 200; i += step) { - c = s->getChar(i) & 0xff; + ts = new TextString(s); + u = ts->getUnicode(); + for (i = 0, j = 0; i < ts->getLength() && j < 200; ++i) { + c = u[i] & 0xff; if (c == '\\') { writePS("\\\\"); j += 2; @@ -6597,4 +7037,5 @@ void PSOutputDev::writePSTextLine(GString *s) { } } writePS("\n"); + delete ts; } diff --git a/xpdf/PSOutputDev.h b/xpdf/PSOutputDev.h index e528f8c..7118830 100644 --- a/xpdf/PSOutputDev.h +++ b/xpdf/PSOutputDev.h @@ -30,11 +30,9 @@ class GfxFont; class GfxColorSpace; class GfxSeparationColorSpace; class PDFRectangle; -struct PST1FontName; -struct PSFont8Info; -struct PSFont16Enc; class PSOutCustomColor; class PSOutputDev; +class PSFontFileInfo; //------------------------------------------------------------------------ // PSOutputDev @@ -92,6 +90,9 @@ public: // Check if file was successfully created. virtual GBool isOk() { return ok; } + // Returns false if there have been any errors on the output stream. + GBool checkIO(); + //---- get info about output device // Does this device use upside-down coordinates? @@ -196,7 +197,7 @@ public: virtual void stroke(GfxState *state); virtual void fill(GfxState *state); virtual void eoFill(GfxState *state); - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -218,15 +219,15 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); #if OPI_SUPPORT //----- OPI functions @@ -262,6 +263,7 @@ public: { overlayCbk = cbk; overlayCbkData = data; } void writePSChar(char c); + void writePSBlock(char *s, int len); void writePS(const char *s); void writePSFmt(const char *fmt, ...); void writePSString(GString *s); @@ -277,27 +279,27 @@ private: void setupResources(Dict *resDict); void setupFonts(Dict *resDict); void setupFont(GfxFont *font, Dict *parentResDict); - void setupEmbeddedType1Font(Ref *id, GString *psName); - void setupExternalType1Font(GString *fileName, GString *psName); - void setupEmbeddedType1CFont(GfxFont *font, Ref *id, GString *psName); - void setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, GString *psName); - void setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, GString *psName); - void setupExternalTrueTypeFont(GfxFont *font, GString *fileName, - GString *psName); - void setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, GString *psName); - void setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, GString *psName, - GBool needVerticalMetrics); - void setupExternalCIDTrueTypeFont(GfxFont *font, - GString *fileName, - GString *psName, - GBool needVerticalMetrics); - void setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, GString *psName); - void setupType3Font(GfxFont *font, GString *psName, Dict *parentResDict); + PSFontFileInfo *setupEmbeddedType1Font(GfxFont *font, Ref *id); + PSFontFileInfo *setupExternalType1Font(GfxFont *font, GString *fileName); + PSFontFileInfo *setupEmbeddedType1CFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupExternalTrueTypeFont(GfxFont *font, GString *fileName, + int fontNum); + PSFontFileInfo *setupEmbeddedCIDType0Font(GfxFont *font, Ref *id); + PSFontFileInfo *setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, + GBool needVerticalMetrics); + PSFontFileInfo *setupExternalCIDTrueTypeFont(GfxFont *font, + GString *fileName, + int fontNum, + GBool needVerticalMetrics); + PSFontFileInfo *setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupType3Font(GfxFont *font, Dict *parentResDict); GString *makePSFontName(GfxFont *font, Ref *id); void setupImages(Dict *resDict); void setupImage(Ref id, Stream *str, GBool mask); void setupForms(Dict *resDict); - void setupForm(Ref id, Object *strObj); + void setupForm(Object *strRef, Object *strObj); void addProcessColor(double c, double m, double y, double k); void addCustomColor(GfxSeparationColorSpace *sepCS); void doPath(GfxPath *path); @@ -358,19 +360,8 @@ private: PDFDoc *doc; XRef *xref; // the xref table for this PDF file - Ref *fontIDs; // list of object IDs of all used fonts - int fontIDLen; // number of entries in fontIDs array - int fontIDSize; // size of fontIDs array - GHash *fontNames; // all used font names - PST1FontName *t1FontNames; // font names for Type 1/1C fonts - int t1FontNameLen; // number of entries in t1FontNames array - int t1FontNameSize; // size of t1FontNames array - PSFont8Info *font8Info; // info for 8-bit fonts - int font8InfoLen; // number of entries in font8Info array - int font8InfoSize; // size of font8Info array - PSFont16Enc *font16Enc; // encodings for substitute 16-bit fonts - int font16EncLen; // number of entries in font16Enc array - int font16EncSize; // size of font16Enc array + GList *fontInfo; // info for each font [PSFontInfo] + GHash *fontFileInfo; // info for each font file [PSFontFileInfo] Ref *imgIDs; // list of image IDs for in-memory images int imgIDLen; // number of entries in imgIDs array int imgIDSize; // size of imgIDs array diff --git a/xpdf/Page.cc b/xpdf/Page.cc index 189d2a2..635ee7a 100644 --- a/xpdf/Page.cc +++ b/xpdf/Page.cc @@ -25,6 +25,7 @@ #include "Gfx.h" #include "GfxState.h" #include "Annot.h" +#include "Form.h" #endif #include "Error.h" #include "Catalog.h" @@ -123,6 +124,15 @@ PageAttrs::PageAttrs(PageAttrs *attrs, Dict *dict) { dict->lookup("Metadata", &metadata); dict->lookup("PieceInfo", &pieceInfo); dict->lookup("SeparationInfo", &separationInfo); + if (dict->lookup("UserUnit", &obj1)->isNum()) { + userUnit = obj1.getNum(); + if (userUnit < 1) { + userUnit = 1; + } + } else { + userUnit = 1; + } + obj1.free(); // resource dictionary dict->lookup("Resources", &obj1); @@ -312,7 +322,7 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, Gfx *gfx; Object obj; Annots *annotList; - Dict *acroForm; + Form *form; int i; if (!out->checkPageSlice(this, hDPI, vDPI, rotate, useMediaBox, crop, @@ -347,8 +357,10 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, contents.fetch(xref, &obj); if (!obj.isNull()) { gfx->saveState(); - gfx->display(&obj); - gfx->restoreState(); + gfx->display(&contents); + while (gfx->getState()->hasSaves()) { + gfx->restoreState(); + } } else { // empty pages need to call dump to do any setup required by the // OutputDev @@ -356,20 +368,11 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, } obj.free(); - // draw annotations + // draw (non-form) annotations if (globalParams->getDrawAnnotations()) { annotList = new Annots(doc, getAnnots(&obj)); obj.free(); - acroForm = doc->getCatalog()->getAcroForm()->isDict() ? - doc->getCatalog()->getAcroForm()->getDict() : NULL; - if (acroForm) { - if (acroForm->lookup("NeedAppearances", &obj)) { - if (obj.isBool() && obj.getBool()) { - annotList->generateAppearances(); - } - } - obj.free(); - } + annotList->generateAnnotAppearances(); if (annotList->getNumAnnots() > 0) { if (globalParams->getPrintCommands()) { printf("***** Annotations\n"); @@ -382,6 +385,12 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, delete annotList; } + // draw form fields + if ((form = doc->getCatalog()->getForm())) { + form->draw(num, gfx, printing); + out->dump(); + } + delete gfx; #endif } @@ -459,6 +468,7 @@ void Page::processLinks(OutputDev *out) { delete links; } +#ifndef PDF_PARSER_ONLY void Page::getDefaultCTM(double *ctm, double hDPI, double vDPI, int rotate, GBool useMediaBox, GBool upsideDown) { GfxState *state; @@ -478,3 +488,4 @@ void Page::getDefaultCTM(double *ctm, double hDPI, double vDPI, } delete state; } +#endif diff --git a/xpdf/Page.h b/xpdf/Page.h index 102eca8..0e7729e 100644 --- a/xpdf/Page.h +++ b/xpdf/Page.h @@ -77,6 +77,7 @@ public: Dict *getSeparationInfo() { return separationInfo.isDict() ? separationInfo.getDict() : (Dict *)NULL; } + double getUserUnit() { return userUnit; } Dict *getResourceDict() { return resources.isDict() ? resources.getDict() : (Dict *)NULL; } @@ -100,6 +101,7 @@ private: Object metadata; Object pieceInfo; Object separationInfo; + double userUnit; Object resources; }; @@ -146,6 +148,7 @@ public: Stream *getMetadata() { return attrs->getMetadata(); } Dict *getPieceInfo() { return attrs->getPieceInfo(); } Dict *getSeparationInfo() { return attrs->getSeparationInfo(); } + double getUserUnit() { return attrs->getUserUnit(); } // Get resource dictionary. Dict *getResourceDict() { return attrs->getResourceDict(); } diff --git a/xpdf/Parser.cc b/xpdf/Parser.cc index c43da26..dd37470 100644 --- a/xpdf/Parser.cc +++ b/xpdf/Parser.cc @@ -153,7 +153,7 @@ Stream *Parser::makeStream(Object *dict, Guchar *fileKey, Object obj; BaseStream *baseStr; Stream *str; - Guint pos, endPos, length; + GFileOffset pos, endPos, length; // get stream start position lexer->skipToNextLine(); @@ -162,20 +162,21 @@ Stream *Parser::makeStream(Object *dict, Guchar *fileKey, } pos = str->getPos(); - // get length - dict->dictLookup("Length", &obj, recursion); - if (obj.isInt()) { - length = (Guint)obj.getInt(); - obj.free(); - } else { - error(errSyntaxError, getPos(), "Bad 'Length' attribute in stream"); - obj.free(); - return NULL; - } - // check for length in damaged file if (xref && xref->getStreamEnd(pos, &endPos)) { length = endPos - pos; + + // get length from the stream object + } else { + dict->dictLookup("Length", &obj, recursion); + if (obj.isInt()) { + length = (GFileOffset)(Guint)obj.getInt(); + obj.free(); + } else { + error(errSyntaxError, getPos(), "Bad 'Length' attribute in stream"); + obj.free(); + return NULL; + } } // in badly damaged PDF files, we can run off the end of the input @@ -210,7 +211,7 @@ Stream *Parser::makeStream(Object *dict, Guchar *fileKey, } // get filters - str = str->addFilters(dict); + str = str->addFilters(dict, recursion); return str; } diff --git a/xpdf/Parser.h b/xpdf/Parser.h index b25d749..d5403d0 100644 --- a/xpdf/Parser.h +++ b/xpdf/Parser.h @@ -42,7 +42,7 @@ public: Stream *getStream() { return lexer->getStream(); } // Get current position in file. - int getPos() { return lexer->getPos(); } + GFileOffset getPos() { return lexer->getPos(); } private: diff --git a/xpdf/PreScanOutputDev.cc b/xpdf/PreScanOutputDev.cc index bf48df0..ced188e 100644 --- a/xpdf/PreScanOutputDev.cc +++ b/xpdf/PreScanOutputDev.cc @@ -61,13 +61,13 @@ void PreScanOutputDev::eoFill(GfxState *state) { } void PreScanOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, - Object *str, + Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep) { if (paintType == 1) { - gfx->drawForm(str, resDict, mat, bbox); + gfx->drawForm(strRef, resDict, mat, bbox); } else { check(state->getFillColorSpace(), state->getFillColor(), state->getFillOpacity(), state->getBlendMode()); @@ -174,9 +174,7 @@ void PreScanOutputDev::endType3Char(GfxState *state) { void PreScanOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { - int i, j; - + GBool inlineImg, GBool interpolate) { check(state->getFillColorSpace(), state->getFillColor(), state->getFillOpacity(), state->getBlendMode()); if (state->getFillColorSpace()->getMode() == csPattern) { @@ -186,9 +184,7 @@ void PreScanOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, if (inlineImg) { str->reset(); - j = height * ((width + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width + 7) / 8)); str->close(); } } @@ -196,9 +192,9 @@ void PreScanOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, void PreScanOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { GfxColorSpace *colorSpace; - int i, j; colorSpace = colorMap->getColorSpace(); if (colorSpace->getMode() == csIndexed) { @@ -221,10 +217,8 @@ void PreScanOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, if (inlineImg) { str->reset(); - j = height * ((width * colorMap->getNumPixelComps() * - colorMap->getBits() + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8)); str->close(); } } @@ -235,7 +229,7 @@ void PreScanOutputDev::drawMaskedImage(GfxState *state, Object *ref, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert) { + GBool maskInvert, GBool interpolate) { GfxColorSpace *colorSpace; colorSpace = colorMap->getColorSpace(); @@ -264,7 +258,8 @@ void PreScanOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap) { + GfxImageColorMap *maskColorMap, + GBool interpolate) { GfxColorSpace *colorSpace; colorSpace = colorMap->getColorSpace(); diff --git a/xpdf/PreScanOutputDev.h b/xpdf/PreScanOutputDev.h index 3889cfc..6ca8b46 100644 --- a/xpdf/PreScanOutputDev.h +++ b/xpdf/PreScanOutputDev.h @@ -67,7 +67,7 @@ public: virtual void stroke(GfxState *state); virtual void fill(GfxState *state); virtual void eoFill(GfxState *state); - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -92,21 +92,22 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap); + GfxImageColorMap *maskColorMap, + GBool interpolate); //----- transparency groups and soft masks virtual void beginTransparencyGroup(GfxState *state, double *bbox, diff --git a/xpdf/SecurityHandler.cc b/xpdf/SecurityHandler.cc index 52607c2..88f2b65 100644 --- a/xpdf/SecurityHandler.cc +++ b/xpdf/SecurityHandler.cc @@ -158,7 +158,7 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, if ((encRevision <= 4 && ownerKeyObj.getString()->getLength() == 32 && userKeyObj.getString()->getLength() == 32) || - (encRevision == 5 && + ((encRevision == 5 || encRevision == 6) && // the spec says 48 bytes, but Acrobat pads them out longer ownerKeyObj.getString()->getLength() >= 48 && userKeyObj.getString()->getLength() >= 48 && @@ -180,7 +180,7 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, //~ doesn't handle the case where StmF, StrF, and EFF are not all the //~ same) if ((encVersion == 4 || encVersion == 5) && - (encRevision == 4 || encRevision == 5)) { + (encRevision == 4 || encRevision == 5 || encRevision == 6)) { encryptDictA->dictLookup("CF", &cryptFiltersObj); encryptDictA->dictLookup("StmF", &streamFilterObj); encryptDictA->dictLookup("StrF", &stringFilterObj); @@ -216,7 +216,9 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, cfLengthObj.free(); } else if (cfmObj.isName("AESV3")) { encVersion = 5; - encRevision = 5; + if (encRevision != 5 && encRevision != 6) { + encRevision = 6; + } encAlgorithm = cryptAES256; if (cryptFilterObj.dictLookup("Length", &cfLengthObj)->isInt()) { @@ -254,15 +256,15 @@ StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA, } else { fileID = new GString(); } - if (fileKeyLength > 16 || fileKeyLength < 0) { + if (fileKeyLength > 16 || fileKeyLength <= 0) { fileKeyLength = 16; } ok = gTrue; - } else if (encVersion == 5 && encRevision == 5) { + } else if (encVersion == 5 && (encRevision == 5 || encRevision == 6)) { fileID = new GString(); // unused for V=R=5 ownerEnc = ownerEncObj.getString()->copy(); userEnc = userEncObj.getString()->copy(); - if (fileKeyLength > 32 || fileKeyLength < 0) { + if (fileKeyLength > 32 || fileKeyLength <= 0) { fileKeyLength = 32; } ok = gTrue; diff --git a/xpdf/SplashOutputDev.cc b/xpdf/SplashOutputDev.cc index 50e4801..d9befec 100644 --- a/xpdf/SplashOutputDev.cc +++ b/xpdf/SplashOutputDev.cc @@ -2,7 +2,7 @@ // // SplashOutputDev.cc // -// Copyright 2003 Glyph & Cog, LLC +// Copyright 2003-2013 Glyph & Cog, LLC // //======================================================================== @@ -27,6 +27,7 @@ #include "BuiltinFont.h" #include "BuiltinFontTables.h" #include "FoFiTrueType.h" +#include "JPXStream.h" #include "SplashBitmap.h" #include "SplashGlyphBitmap.h" #include "SplashPattern.h" @@ -118,7 +119,9 @@ static void splashOutBlendColorDodge(SplashColorPtr src, SplashColorPtr dest, int i, x; for (i = 0; i < splashColorModeNComps[cm]; ++i) { - if (src[i] == 255) { + if (dest[i] == 0) { + blend[i] = 0; + } else if (src[i] == 255) { blend[i] = 255; } else { x = (dest[i] * 255) / (255 - src[i]); @@ -132,7 +135,9 @@ static void splashOutBlendColorBurn(SplashColorPtr src, SplashColorPtr dest, int i, x; for (i = 0; i < splashColorModeNComps[cm]; ++i) { - if (src[i] == 0) { + if (dest[i] == 255) { + blend[i] = 255; + } else if (src[i] == 0) { blend[i] = 0; } else { x = ((255 - dest[i]) * 255) / src[i]; @@ -284,7 +289,10 @@ static void setSat(Guchar rIn, Guchar gIn, Guchar bIn, int sat, static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { - Guchar r0, g0, b0, r1, g1, b1; + Guchar r0, g0, b0; +#if SPLASH_CMYK + Guchar r1, g1, b1; +#endif switch (cm) { case splashModeMono1: @@ -317,7 +325,10 @@ static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest, static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { - Guchar r0, g0, b0, r1, g1, b1; + Guchar r0, g0, b0; +#if SPLASH_CMYK + Guchar r1, g1, b1; +#endif switch (cm) { case splashModeMono1: @@ -435,7 +446,11 @@ SplashBlendFunc splashOutBlendFuncs[] = { class SplashOutFontFileID: public SplashFontFileID { public: - SplashOutFontFileID(Ref *rA) { r = *rA; substIdx = -1; } + SplashOutFontFileID(Ref *rA) { + r = *rA; + substIdx = -1; + oblique = 0; + } ~SplashOutFontFileID() {} @@ -444,12 +459,15 @@ public: ((SplashOutFontFileID *)id)->r.gen == r.gen; } + void setOblique(double obliqueA) { oblique = obliqueA; } + double getOblique() { return oblique; } void setSubstIdx(int substIdxA) { substIdx = substIdxA; } int getSubstIdx() { return substIdx; } private: Ref r; + double oblique; int substIdx; }; @@ -536,6 +554,10 @@ T3FontCache::~T3FontCache() { struct T3GlyphStack { Gushort code; // character code + GBool haveDx; // set after seeing a d0/d1 operator + GBool doNotCache; // set if we see a gsave/grestore before + // the d0/d1 + //----- cache info T3FontCache *cache; // font cache for the current font T3FontCacheTag *cacheTag; // pointer to cache tag for the glyph @@ -580,6 +602,7 @@ SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA, bitmapRowPad = bitmapRowPadA; bitmapTopDown = bitmapTopDownA; bitmapUpsideDown = gFalse; + noComposite = gFalse; allowAntialias = allowAntialiasA; vectorAntialias = allowAntialias && globalParams->getVectorAntialias() && @@ -596,6 +619,7 @@ SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA, colorMode != splashModeMono1, bitmapTopDown); splash = new Splash(bitmap, vectorAntialias, &screenParams); splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(globalParams->getStrokeAdjust()); splash->clear(paperColor, 0); fontEngine = NULL; @@ -688,9 +712,6 @@ void SplashOutputDev::startDoc(XRef *xrefA) { delete fontEngine; } fontEngine = new SplashFontEngine( -#if HAVE_T1LIB_H - globalParams->getEnableT1lib(), -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H globalParams->getEnableFreeType(), globalParams->getDisableFreeTypeHinting() @@ -777,18 +798,28 @@ void SplashOutputDev::startPage(int pageNum, GfxState *state) { } void SplashOutputDev::endPage() { - if (colorMode != splashModeMono1) { + if (colorMode != splashModeMono1 && !noComposite) { splash->compositeBackground(paperColor); } } void SplashOutputDev::saveState(GfxState *state) { splash->saveState(); + if (t3GlyphStack && !t3GlyphStack->haveDx) { + t3GlyphStack->doNotCache = gTrue; + error(errSyntaxWarning, -1, + "Save (q) operator before d0/d1 in Type 3 glyph"); + } } void SplashOutputDev::restoreState(GfxState *state) { splash->restoreState(); needFontUpdate = gTrue; + if (t3GlyphStack && !t3GlyphStack->haveDx) { + t3GlyphStack->doNotCache = gTrue; + error(errSyntaxWarning, -1, + "Restore (Q) operator before d0/d1 in Type 3 glyph"); + } } void SplashOutputDev::updateAll(GfxState *state) { @@ -976,10 +1007,19 @@ void SplashOutputDev::setOverprintMask(GfxColorSpace *colorSpace, if (overprintFlag && globalParams->getOverprintPreview()) { mask = colorSpace->getOverprintMask(); + // The OPM (overprintMode) setting is only relevant when the color + // space is DeviceCMYK or is "implicitly converted to DeviceCMYK". + // Per the PDF spec, this happens with ICCBased color spaces only + // if the profile matches the output device -- Acrobat's output + // preview mode does NOT honor OPM=1 for ICCBased CMYK color + // spaces. To change the behavior here, use: + // if (singleColor && overprintMode && + // (colorSpace->getMode() == csDeviceCMYK || + // (colorSpace->getMode() == csICCBased && + // colorSpace->getNComps() == 4 && + // <...the profile matches...>))) if (singleColor && overprintMode && - (colorSpace->getMode() == csDeviceCMYK || - (colorSpace->getMode() == csICCBased && - colorSpace->getNComps() == 4))) { + colorSpace->getMode() == csDeviceCMYK) { colorSpace->getCMYK(singleColor, &cmyk); if (cmyk.c == 0) { mask &= ~1; @@ -1072,22 +1112,33 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { FoFiTrueType *ff; Ref embRef; Object refObj, strObj; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf; + FILE *extFontFile; +#else GString *tmpFileName, *fileName; FILE *tmpFile; +#endif + char blk[4096]; int *codeToGID; CharCodeToUnicode *ctu; double *textMat; - double m11, m12, m21, m22, fontSize; - double w, fontScaleMin, fontScaleAvg, fontScale; + double m11, m12, m21, m22, fontSize, oblique; + double fsx, fsy, w, fontScaleMin, fontScaleAvg, fontScale; Gushort ww; SplashCoord mat[4]; char *name; Unicode uBuf[8]; - int c, substIdx, n, code, cmap, i; + int substIdx, n, code, cmap, i; needFontUpdate = gFalse; font = NULL; +#if LOAD_FONTS_FROM_MEM + fontBuf = NULL; +#else tmpFileName = NULL; + fileName = NULL; +#endif substIdx = -1; if (!(gfxFont = state->getFont())) { @@ -1098,10 +1149,13 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { goto err1; } - // sanity-check the font size - skip anything larger than 10 inches + // sanity-check the font size - skip anything larger than 20 inches // (this avoids problems allocating memory for the font cache) - if (state->getTransformedFontSize() - > 10 * (state->getHDPI() + state->getVDPI())) { + state->textTransformDelta(state->getFontSize(), state->getFontSize(), + &fsx, &fsy); + state->transformDelta(fsx, fsy, &fsx, &fsy); + if (fabs(fsx) > 20 * state->getHDPI() || + fabs(fsy) > 20 * state->getVDPI()) { goto err1; } @@ -1112,7 +1166,6 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { } else { - fileName = NULL; fontNum = 0; if (!(fontLoc = gfxFont->locateFont(xref, gFalse))) { @@ -1125,6 +1178,24 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { // embedded font if (fontLoc->locType == gfxFontLocEmbedded) { gfxFont->getEmbeddedFontID(&embRef); +#if LOAD_FONTS_FROM_MEM + fontBuf = new GString(); + refObj.initRef(embRef.num, embRef.gen); + refObj.fetch(xref, &strObj); + refObj.free(); + if (!strObj.isStream()) { + error(errSyntaxError, -1, "Embedded font object is wrong type"); + strObj.free(); + delete fontLoc; + goto err2; + } + strObj.streamReset(); + while ((n = strObj.streamGetBlock(blk, sizeof(blk))) > 0) { + fontBuf->append(blk, n); + } + strObj.streamClose(); + strObj.free(); +#else if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { error(errIO, -1, "Couldn't create temporary font file"); delete fontLoc; @@ -1141,21 +1212,39 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { goto err2; } strObj.streamReset(); - while ((c = strObj.streamGetChar()) != EOF) { - fputc(c, tmpFile); + while ((n = strObj.streamGetBlock(blk, sizeof(blk))) > 0) { + fwrite(blk, 1, n, tmpFile); } strObj.streamClose(); strObj.free(); fclose(tmpFile); fileName = tmpFileName; +#endif // external font } else { // gfxFontLocExternal +#if LOAD_FONTS_FROM_MEM + if (!(extFontFile = fopen(fontLoc->path->getCString(), "rb"))) { + error(errSyntaxError, -1, "Couldn't open external font file '{0:t}'", + fontLoc->path); + delete fontLoc; + goto err2; + } + fontBuf = new GString(); + while ((n = fread(blk, 1, sizeof(blk), extFontFile)) > 0) { + fontBuf->append(blk, n); + } + fclose(extFontFile); +#else fileName = fontLoc->path; +#endif fontNum = fontLoc->fontNum; if (fontLoc->substIdx >= 0) { id->setSubstIdx(fontLoc->substIdx); } + if (fontLoc->oblique != 0) { + id->setOblique(fontLoc->oblique); + } } // load the font file @@ -1163,8 +1252,12 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { case fontType1: if (!(fontFile = fontEngine->loadType1Font( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1176,8 +1269,12 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { case fontType1C: if (!(fontFile = fontEngine->loadType1CFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1189,8 +1286,12 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { case fontType1COT: if (!(fontFile = fontEngine->loadOpenTypeT1CFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1201,7 +1302,12 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { break; case fontTrueType: case fontTrueTypeOT: - if ((ff = FoFiTrueType::load(fileName->getCString()))) { +#if LOAD_FONTS_FROM_MEM + if ((ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), + fontNum))) { +#else + if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff); n = 256; delete ff; @@ -1222,9 +1328,13 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { } if (!(fontFile = fontEngine->loadTrueTypeFont( id, - fileName->getCString(), fontNum, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName->getCString(), fileName == tmpFileName, - codeToGID, n, +#endif + fontNum, codeToGID, n, gfxFont->getEmbeddedFontName() ? gfxFont->getEmbeddedFontName()->getCString() : (char *)NULL))) { @@ -1239,8 +1349,14 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { case fontCIDType0C: if (!(fontFile = fontEngine->loadCIDFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf +#else fileName->getCString(), - fileName == tmpFileName))) { + fileName == tmpFileName +#endif + ))) { + error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); @@ -1260,8 +1376,12 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { } if (!(fontFile = fontEngine->loadOpenTypeCFFFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif codeToGID, n))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1281,11 +1401,18 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), n * sizeof(int)); } + } else if (!globalParams->getMapExtTrueTypeFontsViaUnicode()) { + codeToGID = NULL; + n = 0; } else { // create a CID-to-GID mapping, via Unicode if ((ctu = ((GfxCIDFont *)gfxFont)->getToUnicode())) { - //~ this should use fontNum to load the correct font - if ((ff = FoFiTrueType::load(fileName->getCString()))) { +#if LOAD_FONTS_FROM_MEM + if ((ff = FoFiTrueType::make(fontBuf->getCString(), + fontBuf->getLength(), fontNum))) { +#else + if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif // look for a Unicode cmap for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) { if ((ff->getCmapPlatform(cmap) == 3 && @@ -1296,7 +1423,11 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { } if (cmap < ff->getNumCmaps()) { // map CID -> Unicode -> GID - n = ctu->getLength(); + if (ctu->isIdentity()) { + n = 65536; + } else { + n = ctu->getLength(); + } codeToGID = (int *)gmallocn(n, sizeof(int)); for (code = 0; code < n; ++code) { if (ctu->mapToUnicode(code, uBuf, 8) > 0) { @@ -1318,9 +1449,13 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { } if (!(fontFile = fontEngine->loadTrueTypeFont( id, - fileName->getCString(), fontNum, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName->getCString(), fileName == tmpFileName, - codeToGID, n, +#endif + fontNum, codeToGID, n, gfxFont->getEmbeddedFontName() ? gfxFont->getEmbeddedFontName()->getCString() : (char *)NULL))) { @@ -1342,10 +1477,15 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { // get the font matrix textMat = state->getTextMat(); fontSize = state->getFontSize(); - m11 = textMat[0] * fontSize * state->getHorizScaling(); - m12 = textMat[1] * fontSize * state->getHorizScaling(); - m21 = textMat[2] * fontSize; - m22 = textMat[3] * fontSize; + oblique = ((SplashOutFontFileID *)fontFile->getID())->getOblique(); + m11 = state->getHorizScaling() * textMat[0]; + m12 = state->getHorizScaling() * textMat[1]; + m21 = oblique * m11 + textMat[2]; + m22 = oblique * m12 + textMat[3]; + m11 *= fontSize; + m12 *= fontSize; + m21 *= fontSize; + m22 *= fontSize; // for substituted fonts: adjust the font matrix -- compare the // widths of letters and digits (A-Z, a-z, 0-9) in the original font @@ -1393,18 +1533,26 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { mat[2] = m21; mat[3] = m22; font = fontEngine->getFont(fontFile, mat, splash->getMatrix()); +#if !LOAD_FONTS_FROM_MEM if (tmpFileName) { delete tmpFileName; } +#endif return; err2: delete id; err1: +#if LOAD_FONTS_FROM_MEM + if (fontBuf) { + delete fontBuf; + } +#else if (tmpFileName) { unlink(tmpFileName->getCString()); delete tmpFileName; } +#endif return; } @@ -1447,7 +1595,8 @@ void SplashOutputDev::eoFill(GfxState *state) { delete path; } -void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, +void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, + Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -1529,7 +1678,7 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, ya = y * yStep; mat1[4] = xa * mat[0] + ya * mat[2] + mat[4]; mat1[5] = xa * mat[1] + ya * mat[3] + mat[5]; - gfx->drawForm(str, resDict, mat1, bbox); + gfx->drawForm(strRef, resDict, mat1, bbox); } } return; @@ -1542,6 +1691,7 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, colorMode, gTrue, bitmapTopDown); splash = new Splash(bitmap, vectorAntialias, origSplash->getScreen()); splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(globalParams->getStrokeAdjust()); for (i = 0; i < splashMaxColorComps; ++i) { color[i] = 0; } @@ -1556,7 +1706,7 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, // render the tile state->shiftCTM(-tileX0, -tileY0); updateCTM(state, 0, 0, 0, 0, 0, 0); - gfx->drawForm(str, resDict, mat, bbox); + gfx->drawForm(strRef, resDict, mat, bbox); state->shiftCTM(tileX0, tileY0); updateCTM(state, 0, 0, 0, 0, 0, 0); @@ -1889,8 +2039,8 @@ GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, t3GlyphStack->cache = t3Font; t3GlyphStack->cacheTag = NULL; t3GlyphStack->cacheData = NULL; - - haveT3Dx = gFalse; + t3GlyphStack->haveDx = gFalse; + t3GlyphStack->doNotCache = gFalse; return gFalse; } @@ -1920,7 +2070,7 @@ void SplashOutputDev::endType3Char(GfxState *state) { } void SplashOutputDev::type3D0(GfxState *state, double wx, double wy) { - haveT3Dx = gTrue; + t3GlyphStack->haveDx = gTrue; } void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, @@ -1932,10 +2082,14 @@ void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, int i, j; // ignore multiple d0/d1 operators - if (haveT3Dx) { + if (t3GlyphStack->haveDx) { + return; + } + t3GlyphStack->haveDx = gTrue; + // don't cache if we got a gsave/grestore before the d1 + if (t3GlyphStack->doNotCache) { return; } - haveT3Dx = gTrue; t3Font = t3GlyphStack->cache; @@ -2026,6 +2180,7 @@ void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, color[0] = 0xff; } splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(t3GlyphStack->origSplash->getStrokeAdjust()); splash->setFillPattern(new SplashSolidColor(color)); splash->setStrokePattern(new SplashSolidColor(color)); //~ this should copy other state from t3GlyphStack->origSplash? @@ -2072,10 +2227,9 @@ GBool SplashOutputDev::imageMaskSrc(void *data, SplashColorPtr line) { SplashColorPtr q; int x; - if (imgMaskData->y == imgMaskData->height) { - return gFalse; - } - if (!(p = imgMaskData->imgStr->getLine())) { + if (imgMaskData->y == imgMaskData->height || + !(p = imgMaskData->imgStr->getLine())) { + memset(line, 0, imgMaskData->width); return gFalse; } for (x = 0, q = line; x < imgMaskData->width; ++x) { @@ -2087,7 +2241,7 @@ GBool SplashOutputDev::imageMaskSrc(void *data, SplashColorPtr line) { void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageMaskData imgMaskData; @@ -2106,6 +2260,8 @@ void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); + imgMaskData.imgStr = new ImageStream(str, width, 1, 1); imgMaskData.imgStr->reset(); imgMaskData.invert = invert ? 0 : 1; @@ -2114,7 +2270,7 @@ void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, imgMaskData.y = 0; splash->fillImageMask(&imageMaskSrc, &imgMaskData, width, height, mat, - t3GlyphStack != NULL); + t3GlyphStack != NULL, interpolate); if (inlineImg) { while (imgMaskData.y < height) { imgMaskData.imgStr->getLine(); @@ -2130,7 +2286,8 @@ void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, + GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageMaskData imgMaskData; @@ -2145,6 +2302,7 @@ void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, mat[3] = -ctm[3]; mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); imgMaskData.imgStr = new ImageStream(str, width, 1, 1); imgMaskData.imgStr->reset(); imgMaskData.invert = invert ? 0 : 1; @@ -2154,12 +2312,12 @@ void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse); maskSplash = new Splash(maskBitmap, gTrue); - maskColor[0] = 0; - maskSplash->clear(maskColor); + maskSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); + clearMaskRegion(state, maskSplash, 0, 0, 1, 1); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData, - width, height, mat, gFalse); + width, height, mat, gFalse, interpolate); delete imgMaskData.imgStr; str->close(); delete maskSplash; @@ -2180,17 +2338,12 @@ GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine, SplashOutImageData *imgData = (SplashOutImageData *)data; Guchar *p; SplashColorPtr q, col; - GfxRGB rgb; - GfxGray gray; -#if SPLASH_CMYK - GfxCMYK cmyk; -#endif int nComps, x; - if (imgData->y == imgData->height) { - return gFalse; - } - if (!(p = imgData->imgStr->getLine())) { + if (imgData->y == imgData->height || + !(p = imgData->imgStr->getLine())) { + memset(colorLine, 0, + imgData->width * splashColorModeNComps[imgData->colorMode]); return gFalse; } @@ -2229,29 +2382,15 @@ GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine, switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: - for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { - imgData->colorMap->getGray(p, &gray); - *q++ = colToByte(gray); - } + imgData->colorMap->getGrayByteLine(p, colorLine, imgData->width); break; case splashModeRGB8: case splashModeBGR8: - for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { - imgData->colorMap->getRGB(p, &rgb); - *q++ = colToByte(rgb.r); - *q++ = colToByte(rgb.g); - *q++ = colToByte(rgb.b); - } + imgData->colorMap->getRGBByteLine(p, colorLine, imgData->width); break; #if SPLASH_CMYK case splashModeCMYK8: - for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { - imgData->colorMap->getCMYK(p, &cmyk); - *q++ = colToByte(cmyk.c); - *q++ = colToByte(cmyk.m); - *q++ = colToByte(cmyk.y); - *q++ = colToByte(cmyk.k); - } + imgData->colorMap->getCMYKByteLine(p, colorLine, imgData->width); break; #endif } @@ -2274,10 +2413,11 @@ GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine, Guchar alpha; int nComps, x, i; - if (imgData->y == imgData->height) { - return gFalse; - } - if (!(p = imgData->imgStr->getLine())) { + if (imgData->y == imgData->height || + !(p = imgData->imgStr->getLine())) { + memset(colorLine, 0, + imgData->width * splashColorModeNComps[imgData->colorMode]); + memset(alphaLine, 0, imgData->width); return gFalse; } @@ -2353,7 +2493,8 @@ GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine, void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageData imgData; @@ -2378,6 +2519,8 @@ void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); + imgData.imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); @@ -2433,12 +2576,14 @@ void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, if (colorMode == splashModeMono1) { srcMode = splashModeMono8; + } else if (colorMode == splashModeBGR8) { + srcMode = splashModeRGB8; } else { srcMode = colorMode; } src = maskColors ? &alphaImageSrc : &imageSrc; splash->drawImage(src, &imgData, srcMode, maskColors ? gTrue : gFalse, - width, height, mat); + width, height, mat, interpolate); if (inlineImg) { while (imgData.y < height) { imgData.imgStr->getLine(); @@ -2470,15 +2615,17 @@ GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine, #if SPLASH_CMYK GfxCMYK cmyk; #endif + static Guchar bitToByte[2] = {0x00, 0xff}; Guchar alpha; Guchar *maskPtr; - int maskBit; + int maskShift; int nComps, x; - if (imgData->y == imgData->height) { - return gFalse; - } - if (!(p = imgData->imgStr->getLine())) { + if (imgData->y == imgData->height || + !(p = imgData->imgStr->getLine())) { + memset(colorLine, 0, + imgData->width * splashColorModeNComps[imgData->colorMode]); + memset(alphaLine, 0, imgData->width); return gFalse; } @@ -2486,15 +2633,13 @@ GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine, maskPtr = imgData->mask->getDataPtr() + imgData->y * imgData->mask->getRowSize(); - maskBit = 0x80; + maskShift = 7; for (x = 0, q = colorLine, aq = alphaLine; x < imgData->width; ++x, p += nComps) { - alpha = (*maskPtr & maskBit) ? 0xff : 0x00; - if (!(maskBit >>= 1)) { - ++maskPtr; - maskBit = 0x80; - } + alpha = bitToByte[(*maskPtr >> maskShift) & 1]; + maskPtr += (8 - maskShift) >> 3; + maskShift = (maskShift - 1) & 7; if (imgData->lookup) { switch (imgData->colorMode) { case splashModeMono1: @@ -2555,7 +2700,8 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, - int maskHeight, GBool maskInvert) { + int maskHeight, GBool maskInvert, + GBool interpolate) { GfxImageColorMap *maskColorMap; Object maskDecode, decodeLow, decodeHigh; double *ctm; @@ -2577,6 +2723,10 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), NULL); + ctm = state->getCTM(); + reduceImageResolution(str, ctm, &width, &height); + reduceImageResolution(maskStr, ctm, &maskWidth, &maskHeight); + // If the mask is higher resolution than the image, use // drawSoftMaskedImage() instead. if (maskWidth > width || maskHeight > height) { @@ -2589,7 +2739,8 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, new GfxDeviceGrayColorSpace()); maskDecode.free(); drawSoftMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskColorMap); + maskStr, maskWidth, maskHeight, maskColorMap, + interpolate); delete maskColorMap; } else { @@ -2610,19 +2761,20 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, imgMaskData.y = 0; maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, gFalse); maskSplash = new Splash(maskBitmap, gFalse); + maskSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); maskColor[0] = 0; maskSplash->clear(maskColor); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); + // use "glyph mode" here to get the correct scaled size maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData, - maskWidth, maskHeight, mat, gFalse); + maskWidth, maskHeight, mat, gTrue, interpolate); delete imgMaskData.imgStr; maskStr->close(); delete maskSplash; //----- draw the source image - ctm = state->getCTM(); mat[0] = ctm[0]; mat[1] = ctm[1]; mat[2] = -ctm[2]; @@ -2685,11 +2837,13 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, if (colorMode == splashModeMono1) { srcMode = splashModeMono8; + } else if (colorMode == splashModeBGR8) { + srcMode = splashModeRGB8; } else { srcMode = colorMode; } splash->drawImage(&maskedImageSrc, &imgData, srcMode, gTrue, - width, height, mat); + width, height, mat, interpolate); delete maskBitmap; gfree(imgData.lookup); @@ -2703,7 +2857,8 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap) { + GfxImageColorMap *maskColorMap, + GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageData imgData; @@ -2711,7 +2866,6 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, SplashColorMode srcMode; SplashBitmap *maskBitmap; Splash *maskSplash; - SplashColor maskColor; GfxGray gray; GfxRGB rgb; #if SPLASH_CMYK @@ -2731,6 +2885,9 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); + reduceImageResolution(maskStr, ctm, &maskWidth, &maskHeight); + //----- set up the soft mask imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, @@ -2753,10 +2910,10 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse); maskSplash = new Splash(maskBitmap, vectorAntialias); - maskColor[0] = 0; - maskSplash->clear(maskColor); + maskSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); + clearMaskRegion(state, maskSplash, 0, 0, 1, 1); maskSplash->drawImage(&imageSrc, &imgMaskData, splashModeMono8, gFalse, - maskWidth, maskHeight, mat); + maskWidth, maskHeight, mat, interpolate); delete imgMaskData.imgStr; maskStr->close(); gfree(imgMaskData.lookup); @@ -2820,10 +2977,13 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, if (colorMode == splashModeMono1) { srcMode = splashModeMono8; + } else if (colorMode == splashModeBGR8) { + srcMode = splashModeRGB8; } else { srcMode = colorMode; } - splash->drawImage(&imageSrc, &imgData, srcMode, gFalse, width, height, mat); + splash->drawImage(&imageSrc, &imgData, srcMode, gFalse, width, height, mat, + interpolate); splash->setSoftMask(NULL); gfree(imgData.lookup); @@ -2831,6 +2991,98 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, str->close(); } +void SplashOutputDev::reduceImageResolution(Stream *str, double *ctm, + int *width, int *height) { + double sw, sh; + int reduction; + + if (str->getKind() == strJPX && + *width * *height > 10000000) { + sw = (double)*width / (fabs(ctm[2]) + fabs(ctm[3])); + sh = (double)*height / (fabs(ctm[0]) + fabs(ctm[1])); + if (sw > 8 && sh > 8) { + reduction = 3; + } else if (sw > 4 && sh > 4) { + reduction = 2; + } else if (sw > 2 && sh > 2) { + reduction = 1; + } else { + reduction = 0; + } + if (reduction > 0) { + ((JPXStream *)str)->reduceResolution(reduction); + *width >>= reduction; + *height >>= reduction; + } + } +} + +void SplashOutputDev::clearMaskRegion(GfxState *state, + Splash *maskSplash, + double xMin, double yMin, + double xMax, double yMax) { + SplashBitmap *maskBitmap; + double xxMin, yyMin, xxMax, yyMax, xx, yy; + int xxMinI, yyMinI, xxMaxI, yyMaxI, y, n; + Guchar *p; + + maskBitmap = maskSplash->getBitmap(); + xxMin = maskBitmap->getWidth(); + xxMax = 0; + yyMin = maskBitmap->getHeight(); + yyMax = 0; + state->transform(xMin, yMin, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + state->transform(xMin, yMax, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + state->transform(xMax, yMin, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + state->transform(xMax, yMax, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + xxMinI = (int)floor(xxMin); + if (xxMinI < 0) { + xxMinI = 0; + } + xxMaxI = (int)ceil(xxMax); + if (xxMaxI > maskBitmap->getWidth()) { + xxMaxI = maskBitmap->getWidth(); + } + yyMinI = (int)floor(yyMin); + if (yyMinI < 0) { + yyMinI = 0; + } + yyMaxI = (int)ceil(yyMax); + if (yyMaxI > maskBitmap->getHeight()) { + yyMaxI = maskBitmap->getHeight(); + } + p = maskBitmap->getDataPtr() + yyMinI * maskBitmap->getRowSize(); + if (maskBitmap->getMode() == splashModeMono1) { + n = (xxMaxI + 7) / 8 - xxMinI / 8; + p += xxMinI / 8; + } else { + n = xxMaxI - xxMinI; + p += xxMinI; + } + if (xxMaxI > xxMinI) { + for (y = yyMinI; y < yyMaxI; ++y) { + memset(p, 0, n); + p += maskBitmap->getRowSize(); + } + } +} + void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, @@ -2921,7 +3173,7 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, //~ not yet for transparency groups // switch to the blending color space - if (forSoftMask && isolated && blendingColorSpace) { + if (forSoftMask && isolated && !knockout && blendingColorSpace) { if (blendingColorSpace->getMode() == csDeviceGray || blendingColorSpace->getMode() == csCalGray || (blendingColorSpace->getMode() == csICCBased && @@ -2948,6 +3200,7 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, splash = new Splash(bitmap, vectorAntialias, transpGroup->origSplash->getScreen()); splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(globalParams->getStrokeAdjust()); //~ Acrobat apparently copies at least the fill and stroke colors, and //~ maybe other state(?) -- but not the clipping path (and not sure //~ what else) @@ -2962,8 +3215,9 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, splash->clear(color, 0); } else { splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); - splash->setInNonIsolatedGroup(transpGroup->origBitmap, tx, ty); } + splash->setInTransparencyGroup(transpGroup->origBitmap, tx, ty, + !isolated, knockout); transpGroup->tBitmap = bitmap; state->shiftCTM(-tx, -ty); updateCTM(state, 0, 0, 0, 0, 0, 0); @@ -3022,7 +3276,7 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, #if SPLASH_CMYK GfxCMYK cmyk; #endif - double lum, lum2; + double backdrop, backdrop2, lum, lum2; int tx, ty, x, y; tx = transpGroupStack->tx; @@ -3030,11 +3284,13 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, tBitmap = transpGroupStack->tBitmap; // composite with backdrop color + backdrop = 0; if (!alpha && tBitmap->getMode() != splashModeMono1) { //~ need to correctly handle the case where no blending color //~ space is given tSplash = new Splash(tBitmap, vectorAntialias, transpGroupStack->origSplash->getScreen()); + tSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); if (transpGroupStack->blendingColorSpace) { switch (tBitmap->getMode()) { case splashModeMono1: @@ -3042,12 +3298,16 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, break; case splashModeMono8: transpGroupStack->blendingColorSpace->getGray(backdropColor, &gray); + backdrop = colToDbl(gray); color[0] = colToByte(gray); tSplash->compositeBackground(color); break; case splashModeRGB8: case splashModeBGR8: transpGroupStack->blendingColorSpace->getRGB(backdropColor, &rgb); + backdrop = 0.3 * colToDbl(rgb.r) + + 0.59 * colToDbl(rgb.g) + + 0.11 * colToDbl(rgb.b); color[0] = colToByte(rgb.r); color[1] = colToByte(rgb.g); color[2] = colToByte(rgb.b); @@ -3056,6 +3316,13 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, #if SPLASH_CMYK case splashModeCMYK8: transpGroupStack->blendingColorSpace->getCMYK(backdropColor, &cmyk); + backdrop = (1 - colToDbl(cmyk.k)) + - 0.3 * colToDbl(cmyk.c) + - 0.59 * colToDbl(cmyk.m) + - 0.11 * colToDbl(cmyk.y); + if (backdrop < 0) { + backdrop = 0; + } color[0] = colToByte(cmyk.c); color[1] = colToByte(cmyk.m); color[2] = colToByte(cmyk.y); @@ -3067,10 +3334,15 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, delete tSplash; } } + if (transferFunc) { + transferFunc->transform(&backdrop, &backdrop2); + } else { + backdrop2 = backdrop; + } softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse); - memset(softMask->getDataPtr(), 0, + memset(softMask->getDataPtr(), (int)(backdrop2 * 255.0 + 0.5), softMask->getRowSize() * softMask->getHeight()); if (tx < softMask->getWidth() && ty < softMask->getHeight()) { p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx; @@ -3198,12 +3470,19 @@ SplashFont *SplashOutputDev::getFont(GString *name, SplashCoord *textMatA) { Ref ref; SplashOutFontFileID *id; GfxFontLoc *fontLoc; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf; + FILE *extFontFile; + char blk[4096]; + int n; +#endif SplashFontFile *fontFile; SplashFont *fontObj; FoFiTrueType *ff; int *codeToGID; Unicode u; SplashCoord textMat[4]; + SplashCoord oblique; int cmap, i; for (i = 0; i < nBuiltinFonts; ++i) { @@ -3227,11 +3506,40 @@ SplashFont *SplashOutputDev::getFont(GString *name, SplashCoord *textMatA) { if (!(fontLoc = GfxFont::locateBase14Font(name))) { return NULL; } +#if LOAD_FONTS_FROM_MEM + fontBuf = NULL; + if (fontLoc->fontType == fontType1 || + fontLoc->fontType == fontTrueType) { + if (!(extFontFile = fopen(fontLoc->path->getCString(), "rb"))) { + delete fontLoc; + delete id; + return NULL; + } + fontBuf = new GString(); + while ((n = fread(blk, 1, sizeof(blk), extFontFile)) > 0) { + fontBuf->append(blk, n); + } + fclose(extFontFile); + } +#endif if (fontLoc->fontType == fontType1) { - fontFile = fontEngine->loadType1Font(id, fontLoc->path->getCString(), - gFalse, winAnsiEncoding); + fontFile = fontEngine->loadType1Font(id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fontLoc->path->getCString(), + gFalse, +#endif + winAnsiEncoding); } else if (fontLoc->fontType == fontTrueType) { - if (!(ff = FoFiTrueType::load(fontLoc->path->getCString()))) { +#if LOAD_FONTS_FROM_MEM + if (!(ff = FoFiTrueType::make(fontBuf->getCString(), + fontBuf->getLength(), + fontLoc->fontNum))) { +#else + if (!(ff = FoFiTrueType::load(fontLoc->path->getCString(), + fontLoc->fontNum))) { +#endif delete fontLoc; delete id; return NULL; @@ -3259,9 +3567,14 @@ SplashFont *SplashOutputDev::getFont(GString *name, SplashCoord *textMatA) { } delete ff; fontFile = fontEngine->loadTrueTypeFont(id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fontLoc->path->getCString(), + gFalse, +#endif fontLoc->fontNum, - gFalse, codeToGID, 256, NULL); + codeToGID, 256, NULL); } else { delete fontLoc; delete id; @@ -3274,10 +3587,12 @@ SplashFont *SplashOutputDev::getFont(GString *name, SplashCoord *textMatA) { } // create the scaled font + oblique = (SplashCoord) + ((SplashOutFontFileID *)fontFile->getID())->getOblique(); textMat[0] = (SplashCoord)textMatA[0]; textMat[1] = (SplashCoord)textMatA[1]; - textMat[2] = (SplashCoord)textMatA[2]; - textMat[3] = (SplashCoord)textMatA[3]; + textMat[2] = oblique * textMatA[0] + textMatA[2]; + textMat[3] = oblique * textMatA[1] + textMatA[3]; fontObj = fontEngine->getFont(fontFile, textMat, splash->getMatrix()); return fontObj; diff --git a/xpdf/SplashOutputDev.h b/xpdf/SplashOutputDev.h index c48b786..ebbf8a1 100644 --- a/xpdf/SplashOutputDev.h +++ b/xpdf/SplashOutputDev.h @@ -110,7 +110,7 @@ public: virtual void stroke(GfxState *state); virtual void fill(GfxState *state); virtual void eoFill(GfxState *state); - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -135,25 +135,26 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap); + GfxImageColorMap *maskColorMap, + GBool interpolate); //----- Type 3 font operators virtual void type3D0(GfxState *state, double wx, double wy); @@ -194,6 +195,10 @@ public: // for Windows BMP files). void setBitmapUpsideDown(GBool f) { bitmapUpsideDown = f; } + // Setting this to true disables the final composite (with the + // opaque paper color), resulting in transparent output. + void setNoComposite(GBool f) { noComposite = f; } + // Get the Splash object. Splash *getSplash() { return splash; } @@ -245,11 +250,18 @@ private: Guchar *alphaLine); static GBool maskedImageSrc(void *data, SplashColorPtr line, Guchar *alphaLine); + void reduceImageResolution(Stream *str, double *mat, + int *width, int *height); + void clearMaskRegion(GfxState *state, + Splash *maskSplash, + double xMin, double yMin, + double xMax, double yMax); SplashColorMode colorMode; int bitmapRowPad; GBool bitmapTopDown; GBool bitmapUpsideDown; + GBool noComposite; GBool allowAntialias; GBool vectorAntialias; GBool reverseVideo; // reverse video mode @@ -268,7 +280,6 @@ private: t3FontCache[splashOutT3FontCacheSize]; int nT3Fonts; // number of valid entries in t3FontCache T3GlyphStack *t3GlyphStack; // Type 3 glyph context stack - GBool haveT3Dx; // set after seeing a d0/d1 operator SplashFont *font; // current font GBool needFontUpdate; // set when the font needs to be updated diff --git a/xpdf/Stream.cc b/xpdf/Stream.cc index d91b941..7102966 100644 --- a/xpdf/Stream.cc +++ b/xpdf/Stream.cc @@ -16,7 +16,7 @@ #include <stdlib.h> #include <stddef.h> #include <limits.h> -#ifndef WIN32 +#ifndef _WIN32 #include <unistd.h> #endif #include <string.h> @@ -98,11 +98,29 @@ char *Stream::getLine(char *buf, int size) { return buf; } +Guint Stream::discardChars(Guint n) { + char buf[4096]; + Guint count, i, j; + + count = 0; + while (count < n) { + if ((i = n - count) > sizeof(buf)) { + i = (Guint)sizeof(buf); + } + j = (Guint)getBlock(buf, (int)i); + count += j; + if (j != i) { + break; + } + } + return count; +} + GString *Stream::getPSFilter(int psLevel, const char *indent) { return new GString(); } -Stream *Stream::addFilters(Object *dict) { +Stream *Stream::addFilters(Object *dict, int recursion) { Object obj, obj2; Object params, params2; Stream *str; @@ -120,7 +138,7 @@ Stream *Stream::addFilters(Object *dict) { dict->dictLookup("DP", ¶ms); } if (obj.isName()) { - str = makeFilter(obj.getName(), str, ¶ms); + str = makeFilter(obj.getName(), str, ¶ms, recursion); } else if (obj.isArray()) { for (i = 0; i < obj.arrayGetLength(); ++i) { obj.arrayGet(i, &obj2); @@ -129,7 +147,7 @@ Stream *Stream::addFilters(Object *dict) { else params2.initNull(); if (obj2.isName()) { - str = makeFilter(obj2.getName(), str, ¶ms2); + str = makeFilter(obj2.getName(), str, ¶ms2, recursion); } else { error(errSyntaxError, getPos(), "Bad filter name"); str = new EOFStream(str); @@ -146,7 +164,8 @@ Stream *Stream::addFilters(Object *dict) { return str; } -Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { +Stream *Stream::makeFilter(char *name, Stream *str, Object *params, + int recursion) { int pred; // parameters int colors; int bits; @@ -168,23 +187,23 @@ Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { bits = 8; early = 1; if (params->isDict()) { - params->dictLookup("Predictor", &obj); + params->dictLookup("Predictor", &obj, recursion); if (obj.isInt()) pred = obj.getInt(); obj.free(); - params->dictLookup("Columns", &obj); + params->dictLookup("Columns", &obj, recursion); if (obj.isInt()) columns = obj.getInt(); obj.free(); - params->dictLookup("Colors", &obj); + params->dictLookup("Colors", &obj, recursion); if (obj.isInt()) colors = obj.getInt(); obj.free(); - params->dictLookup("BitsPerComponent", &obj); + params->dictLookup("BitsPerComponent", &obj, recursion); if (obj.isInt()) bits = obj.getInt(); obj.free(); - params->dictLookup("EarlyChange", &obj); + params->dictLookup("EarlyChange", &obj, recursion); if (obj.isInt()) early = obj.getInt(); obj.free(); @@ -201,37 +220,37 @@ Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { endOfBlock = gTrue; black = gFalse; if (params->isDict()) { - params->dictLookup("K", &obj); + params->dictLookup("K", &obj, recursion); if (obj.isInt()) { encoding = obj.getInt(); } obj.free(); - params->dictLookup("EndOfLine", &obj); + params->dictLookup("EndOfLine", &obj, recursion); if (obj.isBool()) { endOfLine = obj.getBool(); } obj.free(); - params->dictLookup("EncodedByteAlign", &obj); + params->dictLookup("EncodedByteAlign", &obj, recursion); if (obj.isBool()) { byteAlign = obj.getBool(); } obj.free(); - params->dictLookup("Columns", &obj); + params->dictLookup("Columns", &obj, recursion); if (obj.isInt()) { columns = obj.getInt(); } obj.free(); - params->dictLookup("Rows", &obj); + params->dictLookup("Rows", &obj, recursion); if (obj.isInt()) { rows = obj.getInt(); } obj.free(); - params->dictLookup("EndOfBlock", &obj); + params->dictLookup("EndOfBlock", &obj, recursion); if (obj.isBool()) { endOfBlock = obj.getBool(); } obj.free(); - params->dictLookup("BlackIs1", &obj); + params->dictLookup("BlackIs1", &obj, recursion); if (obj.isBool()) { black = obj.getBool(); } @@ -242,7 +261,7 @@ Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { } else if (!strcmp(name, "DCTDecode") || !strcmp(name, "DCT")) { colorXform = -1; if (params->isDict()) { - if (params->dictLookup("ColorTransform", &obj)->isInt()) { + if (params->dictLookup("ColorTransform", &obj, recursion)->isInt()) { colorXform = obj.getInt(); } obj.free(); @@ -254,19 +273,19 @@ Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { colors = 1; bits = 8; if (params->isDict()) { - params->dictLookup("Predictor", &obj); + params->dictLookup("Predictor", &obj, recursion); if (obj.isInt()) pred = obj.getInt(); obj.free(); - params->dictLookup("Columns", &obj); + params->dictLookup("Columns", &obj, recursion); if (obj.isInt()) columns = obj.getInt(); obj.free(); - params->dictLookup("Colors", &obj); + params->dictLookup("Colors", &obj, recursion); if (obj.isInt()) colors = obj.getInt(); obj.free(); - params->dictLookup("BitsPerComponent", &obj); + params->dictLookup("BitsPerComponent", &obj, recursion); if (obj.isInt()) bits = obj.getInt(); obj.free(); @@ -274,7 +293,7 @@ Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { str = new FlateStream(str, pred, columns, colors, bits); } else if (!strcmp(name, "JBIG2Decode")) { if (params->isDict()) { - params->dictLookup("JBIG2Globals", &globals); + params->dictLookup("JBIG2Globals", &globals, recursion); } str = new JBIG2Stream(str, &globals); globals.free(); @@ -314,7 +333,7 @@ void FilterStream::close() { str->close(); } -void FilterStream::setPos(Guint pos, int dir) { +void FilterStream::setPos(GFileOffset pos, int dir) { error(errInternal, -1, "Called setPos() on FilterStream"); } @@ -365,6 +384,10 @@ void ImageStream::reset() { str->reset(); } +void ImageStream::close() { + str->close(); +} + GBool ImageStream::getPixel(Guchar *pix) { int i; @@ -405,6 +428,10 @@ Guchar *ImageStream::getLine() { } } else if (nBits == 8) { // special case: imgLine == inputLine + } else if (nBits == 16) { + for (i = 0; i < nVals; ++i) { + imgLine[i] = (Guchar)inputLine[2*i]; + } } else { bitMask = (1 << nBits) - 1; buf = 0; @@ -451,8 +478,8 @@ StreamPredictor::StreamPredictor(Stream *strA, int predictorA, return; } predLine = (Guchar *)gmalloc(rowBytes); - memset(predLine, 0, rowBytes); - predIdx = rowBytes; + + reset(); ok = gTrue; } @@ -461,6 +488,11 @@ StreamPredictor::~StreamPredictor() { gfree(predLine); } +void StreamPredictor::reset() { + memset(predLine, 0, rowBytes); + predIdx = rowBytes; +} + int StreamPredictor::lookChar() { if (predIdx >= rowBytes) { if (!getNextLine()) { @@ -573,19 +605,19 @@ GBool StreamPredictor::getNextLine() { // apply TIFF (component) predictor if (predictor == 2) { - if (nBits == 1) { - inBuf = predLine[pixBytes - 1]; - for (i = pixBytes; i < rowBytes; i += 8) { - // 1-bit add is just xor - inBuf = (inBuf << 8) | predLine[i]; - predLine[i] ^= inBuf >> nComps; - } - } else if (nBits == 8) { + if (nBits == 8) { for (i = pixBytes; i < rowBytes; ++i) { predLine[i] += predLine[i - nComps]; } + } else if (nBits == 16) { + for (i = pixBytes; i < rowBytes; i += 2) { + c = ((predLine[i] + predLine[i - 2*nComps]) << 8) + + predLine[i + 1] + predLine[i + 1 - 2*nComps]; + predLine[i] = (Guchar)(c >> 8); + predLine[i+1] = (Guchar)(c & 0xff); + } } else { - memset(upLeftBuf, 0, nComps + 1); + memset(upLeftBuf, 0, nComps); bitMask = (1 << nBits) - 1; inBuf = outBuf = 0; inBits = outBits = 0; @@ -624,8 +656,8 @@ GBool StreamPredictor::getNextLine() { // FileStream //------------------------------------------------------------------------ -FileStream::FileStream(FILE *fA, Guint startA, GBool limitedA, - Guint lengthA, Object *dictA): +FileStream::FileStream(FILE *fA, GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA): BaseStream(dictA) { f = fA; start = startA; @@ -641,22 +673,14 @@ FileStream::~FileStream() { close(); } -Stream *FileStream::makeSubStream(Guint startA, GBool limitedA, - Guint lengthA, Object *dictA) { +Stream *FileStream::makeSubStream(GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA) { return new FileStream(f, startA, limitedA, lengthA, dictA); } void FileStream::reset() { -#if HAVE_FSEEKO - savePos = (Guint)ftello(f); - fseeko(f, start, SEEK_SET); -#elif HAVE_FSEEK64 - savePos = (Guint)ftell64(f); - fseek64(f, start, SEEK_SET); -#else - savePos = (Guint)ftell(f); - fseek(f, start, SEEK_SET); -#endif + savePos = gftell(f); + gfseek(f, start, SEEK_SET); saved = gTrue; bufPtr = bufEnd = buf; bufPos = start; @@ -664,13 +688,7 @@ void FileStream::reset() { void FileStream::close() { if (saved) { -#if HAVE_FSEEKO - fseeko(f, savePos, SEEK_SET); -#elif HAVE_FSEEK64 - fseek64(f, savePos, SEEK_SET); -#else - fseek(f, savePos, SEEK_SET); -#endif + gfseek(f, savePos, SEEK_SET); saved = gFalse; } } @@ -705,7 +723,7 @@ GBool FileStream::fillBuf() { return gFalse; } if (limited && bufPos + fileStreamBufSize > start + length) { - n = start + length - bufPos; + n = (int)(start + length - bufPos); } else { n = fileStreamBufSize; } @@ -717,41 +735,20 @@ GBool FileStream::fillBuf() { return gTrue; } -void FileStream::setPos(Guint pos, int dir) { - Guint size; +void FileStream::setPos(GFileOffset pos, int dir) { + GFileOffset size; if (dir >= 0) { -#if HAVE_FSEEKO - fseeko(f, pos, SEEK_SET); -#elif HAVE_FSEEK64 - fseek64(f, pos, SEEK_SET); -#else - fseek(f, pos, SEEK_SET); -#endif + gfseek(f, pos, SEEK_SET); bufPos = pos; } else { -#if HAVE_FSEEKO - fseeko(f, 0, SEEK_END); - size = (Guint)ftello(f); -#elif HAVE_FSEEK64 - fseek64(f, 0, SEEK_END); - size = (Guint)ftell64(f); -#else - fseek(f, 0, SEEK_END); - size = (Guint)ftell(f); -#endif - if (pos > size) - pos = (Guint)size; -#if HAVE_FSEEKO - fseeko(f, -(int)pos, SEEK_END); - bufPos = (Guint)ftello(f); -#elif HAVE_FSEEK64 - fseek64(f, -(int)pos, SEEK_END); - bufPos = (Guint)ftell64(f); -#else - fseek(f, -(int)pos, SEEK_END); - bufPos = (Guint)ftell(f); -#endif + gfseek(f, 0, SEEK_END); + size = gftell(f); + if (pos > size) { + pos = size; + } + gfseek(f, -pos, SEEK_END); + bufPos = gftell(f); } bufPtr = bufEnd = buf; } @@ -782,17 +779,24 @@ MemStream::~MemStream() { } } -Stream *MemStream::makeSubStream(Guint startA, GBool limited, - Guint lengthA, Object *dictA) { +Stream *MemStream::makeSubStream(GFileOffset startA, GBool limited, + GFileOffset lengthA, Object *dictA) { MemStream *subStr; - Guint newLength; + Guint newStart, newLength; - if (!limited || startA + lengthA > start + length) { - newLength = start + length - startA; + if (startA < start) { + newStart = start; + } else if (startA > start + length) { + newStart = start + (int)length; + } else { + newStart = (int)startA; + } + if (!limited || newStart + lengthA > start + length) { + newLength = start + length - newStart; } else { - newLength = lengthA; + newLength = (Guint)lengthA; } - subStr = new MemStream(buf, startA, newLength, dictA); + subStr = new MemStream(buf, newStart, newLength, dictA); return subStr; } @@ -819,13 +823,13 @@ int MemStream::getBlock(char *blk, int size) { return n; } -void MemStream::setPos(Guint pos, int dir) { +void MemStream::setPos(GFileOffset pos, int dir) { Guint i; if (dir >= 0) { - i = pos; + i = (Guint)pos; } else { - i = start + length - pos; + i = (Guint)(start + length - pos); } if (i < start) { i = start; @@ -846,7 +850,7 @@ void MemStream::moveStart(int delta) { //------------------------------------------------------------------------ EmbedStream::EmbedStream(Stream *strA, Object *dictA, - GBool limitedA, Guint lengthA): + GBool limitedA, GFileOffset lengthA): BaseStream(dictA) { str = strA; limited = limitedA; @@ -856,8 +860,8 @@ EmbedStream::EmbedStream(Stream *strA, Object *dictA, EmbedStream::~EmbedStream() { } -Stream *EmbedStream::makeSubStream(Guint start, GBool limitedA, - Guint lengthA, Object *dictA) { +Stream *EmbedStream::makeSubStream(GFileOffset start, GBool limitedA, + GFileOffset lengthA, Object *dictA) { error(errInternal, -1, "Called makeSubStream() on EmbedStream"); return NULL; } @@ -887,11 +891,11 @@ int EmbedStream::getBlock(char *blk, int size) { return str->getBlock(blk, size); } -void EmbedStream::setPos(Guint pos, int dir) { +void EmbedStream::setPos(GFileOffset pos, int dir) { error(errInternal, -1, "Called setPos() on EmbedStream"); } -Guint EmbedStream::getStart() { +GFileOffset EmbedStream::getStart() { error(errInternal, -1, "Called getStart() on EmbedStream"); return 0; } @@ -1173,6 +1177,9 @@ int LZWStream::getBlock(char *blk, int size) { void LZWStream::reset() { str->reset(); + if (pred) { + pred->reset(); + } eof = gFalse; inputBits = 0; clearTable(); @@ -2068,14 +2075,16 @@ GBool CCITTFaxStream::isBinary(GBool last) { //------------------------------------------------------------------------ // IDCT constants (20.12 fixed point format) -#define dctCos1 4017 // cos(pi/16) -#define dctSin1 799 // sin(pi/16) -#define dctCos3 3406 // cos(3*pi/16) -#define dctSin3 2276 // sin(3*pi/16) -#define dctCos6 1567 // cos(6*pi/16) -#define dctSin6 3784 // sin(6*pi/16) -#define dctSqrt2 5793 // sqrt(2) -#define dctSqrt1d2 2896 // sqrt(2) / 2 +#define dctSqrt2 5793 // sqrt(2) +#define dctSqrt2Cos6 2217 // sqrt(2) * cos(6*pi/16) +#define dctSqrt2Cos6PSin6 7568 // sqrt(2) * (cos(6*pi/16) + sin(6*pi/16)) +#define dctSqrt2Sin6MCos6 3135 // sqrt(2) * (sin(6*pi/16) - cos(6*pi/16)) +#define dctCos3 3406 // cos(3*pi/16) +#define dctCos3PSin3 5681 // cos(3*pi/16) + sin(3*pi/16) +#define dctSin3MCos3 -1130 // sin(3*pi/16) - cos(3*pi/16) +#define dctCos1 4017 // cos(pi/16) +#define dctCos1PSin1 4816 // cos(pi/16) + sin(pi/16) +#define dctSin1MCos1 -3218 // sin(pi/16) - cos(pi/16) // color conversion parameters (16.16 fixed point format) #define dctCrToR 91881 // 1.4020 @@ -2083,10 +2092,47 @@ GBool CCITTFaxStream::isBinary(GBool last) { #define dctCrToG -46802 // -0.71413636 #define dctCbToB 116130 // 1.772 -// clip [-256,511] --> [0,255] -#define dctClipOffset 256 -static Guchar dctClip[768]; -static int dctClipInit = 0; +// The dctClip function clips signed integers to the [0,255] range. +// To handle valid DCT inputs, this must support an input range of at +// least [-256,511]. Invalid DCT inputs (e.g., from damaged PDF +// files) can result in arbitrary values, so we want to mask those +// out. We round the input range size up to a power of 2 (so we can +// use a bit mask), which gives us an input range of [-384,639]. The +// end result is: +// input output +// ---------- ------ +// <-384 X invalid inputs -> output is "don't care" +// -384..-257 0 invalid inputs, clipped +// -256..-1 0 valid inputs, need to be clipped +// 0..255 0..255 +// 256..511 255 valid inputs, need to be clipped +// 512..639 255 invalid inputs, clipped +// >=512 X invalid inputs -> output is "don't care" + +#define dctClipOffset 384 +#define dctClipMask 1023 +static Guchar dctClipData[1024]; + +static inline void dctClipInit() { + static int initDone = 0; + int i; + if (!initDone) { + for (i = -384; i < 0; ++i) { + dctClipData[dctClipOffset + i] = 0; + } + for (i = 0; i < 256; ++i) { + dctClipData[dctClipOffset + i] = i; + } + for (i = 256; i < 639; ++i) { + dctClipData[dctClipOffset + i] = 255; + } + initDone = 1; + } +} + +static inline int dctClip(int x) { + return dctClipData[(dctClipOffset + x) & dctClipMask]; +} // zig zag decode map static int dctZigZag[64] = { @@ -2109,7 +2155,7 @@ static int dctZigZag[64] = { DCTStream::DCTStream(Stream *strA, GBool colorXformA): FilterStream(strA) { - int i, j; + int i; colorXform = colorXformA; progressive = interleaved = gFalse; @@ -2117,23 +2163,15 @@ DCTStream::DCTStream(Stream *strA, GBool colorXformA): mcuWidth = mcuHeight = 0; numComps = 0; comp = 0; - x = y = dy = 0; + x = y = 0; for (i = 0; i < 4; ++i) { - for (j = 0; j < 32; ++j) { - rowBuf[i][j] = NULL; - } frameBuf[i] = NULL; } + rowBuf = NULL; + memset(dcHuffTables, 0, sizeof(dcHuffTables)); + memset(acHuffTables, 0, sizeof(acHuffTables)); - if (!dctClipInit) { - for (i = -256; i < 0; ++i) - dctClip[dctClipOffset + i] = 0; - for (i = 0; i < 256; ++i) - dctClip[dctClipOffset + i] = i; - for (i = 256; i < 512; ++i) - dctClip[dctClipOffset + i] = 255; - dctClipInit = 1; - } + dctClipInit(); } DCTStream::~DCTStream() { @@ -2142,7 +2180,7 @@ DCTStream::~DCTStream() { } void DCTStream::reset() { - int i, j; + int i; str->reset(); @@ -2157,6 +2195,8 @@ void DCTStream::reset() { restartInterval = 0; if (!readHeader()) { + // force an EOF condition + progressive = gTrue; y = height; return; } @@ -2229,17 +2269,11 @@ void DCTStream::reset() { // allocate a buffer for one row of MCUs bufWidth = ((width + mcuWidth - 1) / mcuWidth) * mcuWidth; - for (i = 0; i < numComps; ++i) { - for (j = 0; j < mcuHeight; ++j) { - rowBuf[i][j] = (Guchar *)gmallocn(bufWidth, sizeof(Guchar)); - } - } + rowBuf = (Guchar *)gmallocn(numComps * mcuHeight, bufWidth); + rowBufPtr = rowBufEnd = rowBuf; // initialize counters - comp = 0; - x = 0; - y = 0; - dy = mcuHeight; + y = -mcuHeight; restartMarker = 0xd0; restart(); @@ -2247,26 +2281,24 @@ void DCTStream::reset() { } void DCTStream::close() { - int i, j; + int i; for (i = 0; i < 4; ++i) { - for (j = 0; j < 32; ++j) { - gfree(rowBuf[i][j]); - rowBuf[i][j] = NULL; - } gfree(frameBuf[i]); frameBuf[i] = NULL; } + gfree(rowBuf); + rowBuf = NULL; FilterStream::close(); } int DCTStream::getChar() { int c; - if (y >= height) { - return EOF; - } if (progressive || !interleaved) { + if (y >= height) { + return EOF; + } c = frameBuf[comp][y * bufWidth + x]; if (++comp == numComps) { comp = 0; @@ -2276,48 +2308,38 @@ int DCTStream::getChar() { } } } else { - if (dy >= mcuHeight) { + if (rowBufPtr == rowBufEnd) { + if (y + mcuHeight >= height) { + return EOF; + } + y += mcuHeight; if (!readMCURow()) { y = height; return EOF; } - comp = 0; - x = 0; - dy = 0; - } - c = rowBuf[comp][dy][x]; - if (++comp == numComps) { - comp = 0; - if (++x == width) { - x = 0; - ++y; - ++dy; - if (y == height) { - readTrailer(); - } - } } + c = *rowBufPtr++; } return c; } int DCTStream::lookChar() { - if (y >= height) { - return EOF; - } if (progressive || !interleaved) { + if (y >= height) { + return EOF; + } return frameBuf[comp][y * bufWidth + x]; } else { - if (dy >= mcuHeight) { + if (rowBufPtr == rowBufEnd) { + if (y + mcuHeight >= height) { + return EOF; + } if (!readMCURow()) { y = height; return EOF; } - comp = 0; - x = 0; - dy = 0; } - return rowBuf[comp][dy][x]; + return *rowBufPtr; } } @@ -2375,38 +2397,49 @@ GBool DCTStream::readMCURow() { } transformDataUnit(quantTables[compInfo[cc].quantTable], data1, data2); - if (hSub == 1 && vSub == 1) { + if (hSub == 1 && vSub == 1 && x1+x2+8 <= width) { for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) { - p1 = &rowBuf[cc][y2+y3][x1+x2]; - p1[0] = data2[i]; - p1[1] = data2[i+1]; - p1[2] = data2[i+2]; - p1[3] = data2[i+3]; - p1[4] = data2[i+4]; - p1[5] = data2[i+5]; - p1[6] = data2[i+6]; - p1[7] = data2[i+7]; + p1 = &rowBuf[((y2+y3) * width + (x1+x2)) * numComps + cc]; + p1[0] = data2[i]; + p1[ numComps] = data2[i+1]; + p1[2*numComps] = data2[i+2]; + p1[3*numComps] = data2[i+3]; + p1[4*numComps] = data2[i+4]; + p1[5*numComps] = data2[i+5]; + p1[6*numComps] = data2[i+6]; + p1[7*numComps] = data2[i+7]; } - } else if (hSub == 2 && vSub == 2) { + } else if (hSub == 2 && vSub == 2 && x1+x2+16 <= width) { for (y3 = 0, i = 0; y3 < 16; y3 += 2, i += 8) { - p1 = &rowBuf[cc][y2+y3][x1+x2]; - p2 = &rowBuf[cc][y2+y3+1][x1+x2]; - p1[0] = p1[1] = p2[0] = p2[1] = data2[i]; - p1[2] = p1[3] = p2[2] = p2[3] = data2[i+1]; - p1[4] = p1[5] = p2[4] = p2[5] = data2[i+2]; - p1[6] = p1[7] = p2[6] = p2[7] = data2[i+3]; - p1[8] = p1[9] = p2[8] = p2[9] = data2[i+4]; - p1[10] = p1[11] = p2[10] = p2[11] = data2[i+5]; - p1[12] = p1[13] = p2[12] = p2[13] = data2[i+6]; - p1[14] = p1[15] = p2[14] = p2[15] = data2[i+7]; + p1 = &rowBuf[((y2+y3) * width + (x1+x2)) * numComps + cc]; + p2 = p1 + width * numComps; + p1[0] = p1[numComps] = + p2[0] = p2[numComps] = data2[i]; + p1[2*numComps] = p1[3*numComps] = + p2[2*numComps] = p2[3*numComps] = data2[i+1]; + p1[4*numComps] = p1[5*numComps] = + p2[4*numComps] = p2[5*numComps] = data2[i+2]; + p1[6*numComps] = p1[7*numComps] = + p2[6*numComps] = p2[7*numComps] = data2[i+3]; + p1[8*numComps] = p1[9*numComps] = + p2[8*numComps] = p2[9*numComps] = data2[i+4]; + p1[10*numComps] = p1[11*numComps] = + p2[10*numComps] = p2[11*numComps] = data2[i+5]; + p1[12*numComps] = p1[13*numComps] = + p2[12*numComps] = p2[13*numComps] = data2[i+6]; + p1[14*numComps] = p1[15*numComps] = + p2[14*numComps] = p2[15*numComps] = data2[i+7]; } } else { + p1 = &rowBuf[(y2 * width + (x1+x2)) * numComps + cc]; i = 0; for (y3 = 0, y4 = 0; y3 < 8; ++y3, y4 += vSub) { for (x3 = 0, x4 = 0; x3 < 8; ++x3, x4 += hSub) { - for (y5 = 0; y5 < vSub; ++y5) - for (x5 = 0; x5 < hSub; ++x5) - rowBuf[cc][y2+y4+y5][x1+x2+x4+x5] = data2[i]; + for (y5 = 0; y5 < vSub; ++y5) { + for (x5 = 0; x5 < hSub && x1+x2+x4+x5 < width; ++x5) { + p1[((y4+y5) * width + (x4+x5)) * numComps] = data2[i]; + } + } ++i; } } @@ -2415,42 +2448,46 @@ GBool DCTStream::readMCURow() { } } --restartCtr; + } - // color space conversion - if (colorXform) { - // convert YCbCr to RGB - if (numComps == 3) { - for (y2 = 0; y2 < mcuHeight; ++y2) { - for (x2 = 0; x2 < mcuWidth; ++x2) { - pY = rowBuf[0][y2][x1+x2]; - pCb = rowBuf[1][y2][x1+x2] - 128; - pCr = rowBuf[2][y2][x1+x2] - 128; - pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - rowBuf[0][y2][x1+x2] = dctClip[dctClipOffset + pR]; - pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - rowBuf[1][y2][x1+x2] = dctClip[dctClipOffset + pG]; - pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - rowBuf[2][y2][x1+x2] = dctClip[dctClipOffset + pB]; - } - } - // convert YCbCrK to CMYK (K is passed through unchanged) - } else if (numComps == 4) { - for (y2 = 0; y2 < mcuHeight; ++y2) { - for (x2 = 0; x2 < mcuWidth; ++x2) { - pY = rowBuf[0][y2][x1+x2]; - pCb = rowBuf[1][y2][x1+x2] - 128; - pCr = rowBuf[2][y2][x1+x2] - 128; - pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - rowBuf[0][y2][x1+x2] = 255 - dctClip[dctClipOffset + pR]; - pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - rowBuf[1][y2][x1+x2] = 255 - dctClip[dctClipOffset + pG]; - pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - rowBuf[2][y2][x1+x2] = 255 - dctClip[dctClipOffset + pB]; - } - } + // color space conversion + if (colorXform) { + // convert YCbCr to RGB + if (numComps == 3) { + for (i = 0, p1 = rowBuf; i < width * mcuHeight; ++i, p1 += 3) { + pY = p1[0]; + pCb = p1[1] - 128; + pCr = p1[2] - 128; + pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; + p1[0] = dctClip(pR); + pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; + p1[1] = dctClip(pG); + pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; + p1[2] = dctClip(pB); + } + // convert YCbCrK to CMYK (K is passed through unchanged) + } else if (numComps == 4) { + for (i = 0, p1 = rowBuf; i < width * mcuHeight; ++i, p1 += 4) { + pY = p1[0]; + pCb = p1[1] - 128; + pCr = p1[2] - 128; + pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; + p1[0] = 255 - dctClip(pR); + pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; + p1[1] = 255 - dctClip(pG); + pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; + p1[2] = 255 - dctClip(pB); } } } + + rowBufPtr = rowBuf; + if (y + mcuHeight <= height) { + rowBufEnd = rowBuf + numComps * width * mcuHeight; + } else { + rowBufEnd = rowBuf + numComps * width * (height - y); + } + return gTrue; } @@ -2609,7 +2646,7 @@ GBool DCTStream::readDataUnit(DCTHuffTable *dcHuffTable, return gTrue; } -// Read one data unit from a sequential JPEG stream. +// Read one data unit from a progressive JPEG stream. GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, DCTHuffTable *acHuffTable, int *prevDC, int data[64]) { @@ -2635,7 +2672,13 @@ GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, if ((bit = readBit()) == 9999) { return gFalse; } - data[0] += bit << scanInfo.al; + if (bit) { + if (data[0] >= 0) { + data[0] += 1 << scanInfo.al; + } else { + data[0] -= 1 << scanInfo.al; + } + } } ++i; } @@ -2652,7 +2695,11 @@ GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } } } @@ -2678,7 +2725,11 @@ GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } } } @@ -2701,7 +2752,11 @@ GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } } } @@ -2723,7 +2778,11 @@ GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } j = dctZigZag[i++]; } @@ -2837,12 +2896,12 @@ void DCTStream::decodeImage() { pCb = *p1 - 128; pCr = *p2 - 128; pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - *p0++ = dctClip[dctClipOffset + pR]; + *p0++ = dctClip(pR); pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - *p1++ = dctClip[dctClipOffset + pG]; + *p1++ = dctClip(pG); pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - *p2++ = dctClip[dctClipOffset + pB]; + *p2++ = dctClip(pB); } } // convert YCbCrK to CMYK (K is passed through unchanged) @@ -2856,12 +2915,12 @@ void DCTStream::decodeImage() { pCb = *p1 - 128; pCr = *p2 - 128; pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - *p0++ = 255 - dctClip[dctClipOffset + pR]; + *p0++ = 255 - dctClip(pR); pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - *p1++ = 255 - dctClip[dctClipOffset + pG]; + *p1++ = 255 - dctClip(pG); pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - *p2++ = 255 - dctClip[dctClipOffset + pB]; + *p2++ = 255 - dctClip(pB); } } } @@ -2880,71 +2939,76 @@ void DCTStream::decodeImage() { // paper. void DCTStream::transformDataUnit(Gushort *quantTable, int dataIn[64], Guchar dataOut[64]) { - int v0, v1, v2, v3, v4, v5, v6, v7, t; + int v0, v1, v2, v3, v4, v5, v6, v7, t0, t1, t2; int *p; + Gushort *q; int i; - // dequant - for (i = 0; i < 64; ++i) { - dataIn[i] *= quantTable[i]; - } - - // inverse DCT on rows + // dequant; inverse DCT on rows for (i = 0; i < 64; i += 8) { p = dataIn + i; + q = quantTable + i; // check for all-zero AC coefficients if (p[1] == 0 && p[2] == 0 && p[3] == 0 && p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] == 0) { - t = (dctSqrt2 * p[0] + 512) >> 10; - p[0] = t; - p[1] = t; - p[2] = t; - p[3] = t; - p[4] = t; - p[5] = t; - p[6] = t; - p[7] = t; + t0 = p[0] * q[0]; + p[0] = t0; + p[1] = t0; + p[2] = t0; + p[3] = t0; + p[4] = t0; + p[5] = t0; + p[6] = t0; + p[7] = t0; continue; } // stage 4 - v0 = (dctSqrt2 * p[0] + 128) >> 8; - v1 = (dctSqrt2 * p[4] + 128) >> 8; - v2 = p[2]; - v3 = p[6]; - v4 = (dctSqrt1d2 * (p[1] - p[7]) + 128) >> 8; - v7 = (dctSqrt1d2 * (p[1] + p[7]) + 128) >> 8; - v5 = p[3] << 4; - v6 = p[5] << 4; + v0 = p[0] * q[0]; + v1 = p[4] * q[4]; + v2 = p[2] * q[2]; + v3 = p[6] * q[6]; + t0 = p[1] * q[1]; + t1 = p[7] * q[7]; + v4 = t0 - t1; + v7 = t0 + t1; + v5 = (dctSqrt2 * p[3] * q[3]) >> 12; + v6 = (dctSqrt2 * p[5] * q[5]) >> 12; // stage 3 - t = (v0 - v1+ 1) >> 1; - v0 = (v0 + v1 + 1) >> 1; - v1 = t; - t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; - v3 = t; - t = (v4 - v6 + 1) >> 1; - v4 = (v4 + v6 + 1) >> 1; - v6 = t; - t = (v7 + v5 + 1) >> 1; - v5 = (v7 - v5 + 1) >> 1; - v7 = t; + t0 = v0 - v1; + v0 = v0 + v1; + v1 = t0; + t0 = dctSqrt2Cos6 * (v2 + v3); + t1 = dctSqrt2Cos6PSin6 * v3; + t2 = dctSqrt2Sin6MCos6 * v2; + v2 = (t0 - t1) >> 12; + v3 = (t0 + t2) >> 12; + t0 = v4 - v6; + v4 = v4 + v6; + v6 = t0; + t0 = v7 + v5; + v5 = v7 - v5; + v7 = t0; // stage 2 - t = (v0 - v3 + 1) >> 1; - v0 = (v0 + v3 + 1) >> 1; - v3 = t; - t = (v1 - v2 + 1) >> 1; - v1 = (v1 + v2 + 1) >> 1; - v2 = t; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; + t0 = v0 - v3; + v0 = v0 + v3; + v3 = t0; + t0 = v1 - v2; + v1 = v1 + v2; + v2 = t0; + t0 = dctCos3 * (v4 + v7); + t1 = dctCos3PSin3 * v7; + t2 = dctSin3MCos3 * v4; + v4 = (t0 - t1) >> 12; + v7 = (t0 + t2) >> 12; + t0 = dctCos1 * (v5 + v6); + t1 = dctCos1PSin1 * v6; + t2 = dctSin1MCos1 * v5; + v5 = (t0 - t1) >> 12; + v6 = (t0 + t2) >> 12; // stage 1 p[0] = v0 + v7; @@ -2964,55 +3028,60 @@ void DCTStream::transformDataUnit(Gushort *quantTable, // check for all-zero AC coefficients if (p[1*8] == 0 && p[2*8] == 0 && p[3*8] == 0 && p[4*8] == 0 && p[5*8] == 0 && p[6*8] == 0 && p[7*8] == 0) { - t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14; - p[0*8] = t; - p[1*8] = t; - p[2*8] = t; - p[3*8] = t; - p[4*8] = t; - p[5*8] = t; - p[6*8] = t; - p[7*8] = t; + t0 = p[0*8]; + p[1*8] = t0; + p[2*8] = t0; + p[3*8] = t0; + p[4*8] = t0; + p[5*8] = t0; + p[6*8] = t0; + p[7*8] = t0; continue; } // stage 4 - v0 = (dctSqrt2 * p[0*8] + 2048) >> 12; - v1 = (dctSqrt2 * p[4*8] + 2048) >> 12; + v0 = p[0*8]; + v1 = p[4*8]; v2 = p[2*8]; v3 = p[6*8]; - v4 = (dctSqrt1d2 * (p[1*8] - p[7*8]) + 2048) >> 12; - v7 = (dctSqrt1d2 * (p[1*8] + p[7*8]) + 2048) >> 12; - v5 = p[3*8]; - v6 = p[5*8]; + v4 = p[1*8] - p[7*8]; + v7 = p[1*8] + p[7*8]; + v5 = (dctSqrt2 * p[3*8]) >> 12; + v6 = (dctSqrt2 * p[5*8]) >> 12; // stage 3 - t = (v0 - v1 + 1) >> 1; - v0 = (v0 + v1 + 1) >> 1; - v1 = t; - t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; - v3 = t; - t = (v4 - v6 + 1) >> 1; - v4 = (v4 + v6 + 1) >> 1; - v6 = t; - t = (v7 + v5 + 1) >> 1; - v5 = (v7 - v5 + 1) >> 1; - v7 = t; + t0 = v0 - v1; + v0 = v0 + v1; + v1 = t0; + t0 = dctSqrt2Cos6 * (v2 + v3); + t1 = dctSqrt2Cos6PSin6 * v3; + t2 = dctSqrt2Sin6MCos6 * v2; + v2 = (t0 - t1) >> 12; + v3 = (t0 + t2) >> 12; + t0 = v4 - v6; + v4 = v4 + v6; + v6 = t0; + t0 = v7 + v5; + v5 = v7 - v5; + v7 = t0; // stage 2 - t = (v0 - v3 + 1) >> 1; - v0 = (v0 + v3 + 1) >> 1; - v3 = t; - t = (v1 - v2 + 1) >> 1; - v1 = (v1 + v2 + 1) >> 1; - v2 = t; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; + t0 = v0 - v3; + v0 = v0 + v3; + v3 = t0; + t0 = v1 - v2; + v1 = v1 + v2; + v2 = t0; + t0 = dctCos3 * (v4 + v7); + t1 = dctCos3PSin3 * v7; + t2 = dctSin3MCos3 * v4; + v4 = (t0 - t1) >> 12; + v7 = (t0 + t2) >> 12; + t0 = dctCos1 * (v5 + v6); + t1 = dctCos1PSin1 * v6; + t2 = dctSin1MCos1 * v5; + v5 = (t0 - t1) >> 12; + v6 = (t0 + t2) >> 12; // stage 1 p[0*8] = v0 + v7; @@ -3027,7 +3096,7 @@ void DCTStream::transformDataUnit(Gushort *quantTable, // convert to 8-bit integers for (i = 0; i < 64; ++i) { - dataOut[i] = dctClip[dctClipOffset + 128 + ((dataIn[i] + 8) >> 4)]; + dataOut[i] = dctClip(128 + (dataIn[i] >> 3)); } } @@ -3103,7 +3172,6 @@ GBool DCTStream::readHeader() { GBool doScan; int n; int c = 0; - int i; // read headers doScan = gFalse; @@ -3163,9 +3231,7 @@ GBool DCTStream::readHeader() { // skip APPn / COM / etc. if (c >= 0xe0) { n = read16() - 2; - for (i = 0; i < n; ++i) { - str->getChar(); - } + str->discardChars(n); } else { error(errSyntaxError, getPos(), "Unknown DCT marker <{0:02x}>", c); return gFalse; @@ -3178,12 +3244,11 @@ GBool DCTStream::readHeader() { } GBool DCTStream::readBaselineSOF() { - int length; int prec; int i; int c; - length = read16(); + read16(); // length prec = str->getChar(); height = read16(); width = read16(); @@ -3218,12 +3283,11 @@ GBool DCTStream::readBaselineSOF() { } GBool DCTStream::readProgressiveSOF() { - int length; int prec; int i; int c; - length = read16(); + read16(); // length prec = str->getChar(); height = read16(); width = read16(); @@ -4194,6 +4258,9 @@ void FlateStream::reset() { eof = gTrue; str->reset(); + if (pred) { + pred->reset(); + } // read header //~ need to look at window size? @@ -4274,10 +4341,10 @@ int FlateStream::getBlock(char *blk, int size) { n = 0; while (n < size) { - if (endOfBlock && eof) { - break; - } if (remain == 0) { + if (endOfBlock && eof) { + break; + } readSome(); } while (remain && n < size) { @@ -4969,3 +5036,149 @@ GBool RunLengthEncoder::fillBuf() { bufPtr = buf; return gTrue; } + +//------------------------------------------------------------------------ +// LZWEncoder +//------------------------------------------------------------------------ + +LZWEncoder::LZWEncoder(Stream *strA): + FilterStream(strA) +{ + inBufLen = 0; + outBufLen = 0; +} + +LZWEncoder::~LZWEncoder() { + if (str->isEncoder()) { + delete str; + } +} + +void LZWEncoder::reset() { + int i; + + str->reset(); + + // initialize code table + for (i = 0; i < 256; ++i) { + table[i].byte = i; + table[i].next = NULL; + table[i].children = NULL; + } + nextSeq = 258; + codeLen = 9; + + // initialize input buffer + inBufLen = str->getBlock((char *)inBuf, sizeof(inBuf)); + + // initialize output buffer with a clear-table code + outBuf = 256; + outBufLen = 9; + needEOD = gFalse; +} + +int LZWEncoder::getChar() { + int ret; + + if (inBufLen == 0 && !needEOD && outBufLen == 0) { + return EOF; + } + if (outBufLen < 8 && (inBufLen > 0 || needEOD)) { + fillBuf(); + } + if (outBufLen >= 8) { + ret = (outBuf >> (outBufLen - 8)) & 0xff; + outBufLen -= 8; + } else { + ret = (outBuf << (8 - outBufLen)) & 0xff; + outBufLen = 0; + } + return ret; +} + +int LZWEncoder::lookChar() { + if (inBufLen == 0 && !needEOD && outBufLen == 0) { + return EOF; + } + if (outBufLen < 8 && (inBufLen > 0 || needEOD)) { + fillBuf(); + } + if (outBufLen >= 8) { + return (outBuf >> (outBufLen - 8)) & 0xff; + } else { + return (outBuf << (8 - outBufLen)) & 0xff; + } +} + +// On input, outBufLen < 8. +// This function generates, at most, 2 12-bit codes +// --> outBufLen < 8 + 12 + 12 = 32 +void LZWEncoder::fillBuf() { + LZWEncoderNode *p0, *p1; + int seqLen, code, i; + + if (needEOD) { + outBuf = (outBuf << codeLen) | 257; + outBufLen += codeLen; + needEOD = gFalse; + return; + } + + // find longest matching sequence (if any) + p0 = table + inBuf[0]; + seqLen = 1; + while (inBufLen > seqLen) { + for (p1 = p0->children; p1; p1 = p1->next) { + if (p1->byte == inBuf[seqLen]) { + break; + } + } + if (!p1) { + break; + } + p0 = p1; + ++seqLen; + } + code = (int)(p0 - table); + + // generate an output code + outBuf = (outBuf << codeLen) | code; + outBufLen += codeLen; + + // update the table + table[nextSeq].byte = seqLen < inBufLen ? inBuf[seqLen] : 0; + table[nextSeq].children = NULL; + if (table[code].children) { + table[nextSeq].next = table[code].children; + } else { + table[nextSeq].next = NULL; + } + table[code].children = table + nextSeq; + ++nextSeq; + + // update the input buffer + memmove(inBuf, inBuf + seqLen, inBufLen - seqLen); + inBufLen -= seqLen; + inBufLen += str->getBlock((char *)inBuf + inBufLen, + sizeof(inBuf) - inBufLen); + + // increment codeLen; generate clear-table code + if (nextSeq == (1 << codeLen)) { + ++codeLen; + if (codeLen == 13) { + outBuf = (outBuf << 12) | 256; + outBufLen += 12; + for (i = 0; i < 256; ++i) { + table[i].next = NULL; + table[i].children = NULL; + } + nextSeq = 258; + codeLen = 9; + } + } + + // generate EOD next time + if (inBufLen == 0) { + needEOD = gTrue; + } +} diff --git a/xpdf/Stream.h b/xpdf/Stream.h index 50710a6..c55d802 100644 --- a/xpdf/Stream.h +++ b/xpdf/Stream.h @@ -17,6 +17,7 @@ #include <stdio.h> #include "gtypes.h" +#include "gfile.h" #include "Object.h" class BaseStream; @@ -97,13 +98,18 @@ public: // Get next line from stream. virtual char *getLine(char *buf, int size); + // Discard the next <n> bytes from stream. Returns the number of + // bytes discarded, which will be less than <n> only if EOF is + // reached. + virtual Guint discardChars(Guint n); + // Get current position in file. - virtual int getPos() = 0; + virtual GFileOffset getPos() = 0; // Go to a position in the stream. If <dir> is negative, the // position is from the end of the file; otherwise the position is // from the start of the file. - virtual void setPos(Guint pos, int dir = 0) = 0; + virtual void setPos(GFileOffset pos, int dir = 0) = 0; // Get PostScript command for the filter(s). virtual GString *getPSFilter(int psLevel, const char *indent); @@ -133,11 +139,11 @@ public: // Add filters to this stream according to the parameters in <dict>. // Returns the new stream. - Stream *addFilters(Object *dict); + Stream *addFilters(Object *dict, int recursion = 0); private: - Stream *makeFilter(char *name, Stream *str, Object *params); + Stream *makeFilter(char *name, Stream *str, Object *params, int recursion); int ref; // reference count }; @@ -153,9 +159,9 @@ public: BaseStream(Object *dictA); virtual ~BaseStream(); - virtual Stream *makeSubStream(Guint start, GBool limited, - Guint length, Object *dict) = 0; - virtual void setPos(Guint pos, int dir = 0) = 0; + virtual Stream *makeSubStream(GFileOffset start, GBool limited, + GFileOffset length, Object *dict) = 0; + virtual void setPos(GFileOffset pos, int dir = 0) = 0; virtual GBool isBinary(GBool last = gTrue) { return last; } virtual BaseStream *getBaseStream() { return this; } virtual Stream *getUndecodedStream() { return this; } @@ -163,7 +169,7 @@ public: virtual GString *getFileName() { return NULL; } // Get/set position of first byte of stream within the file. - virtual Guint getStart() = 0; + virtual GFileOffset getStart() = 0; virtual void moveStart(int delta) = 0; private: @@ -183,8 +189,8 @@ public: FilterStream(Stream *strA); virtual ~FilterStream(); virtual void close(); - virtual int getPos() { return str->getPos(); } - virtual void setPos(Guint pos, int dir = 0); + virtual GFileOffset getPos() { return str->getPos(); } + virtual void setPos(GFileOffset pos, int dir = 0); virtual BaseStream *getBaseStream() { return str->getBaseStream(); } virtual Stream *getUndecodedStream() { return str->getUndecodedStream(); } virtual Dict *getDict() { return str->getDict(); } @@ -212,6 +218,9 @@ public: // Reset the stream. void reset(); + // Close down the stream. + void close(); + // Gets the next pixel from the stream. <pix> should be able to hold // at least nComps elements. Returns false at end of file. GBool getPixel(Guchar *pix); @@ -252,6 +261,8 @@ public: GBool isOk() { return ok; } + void reset(); + int lookChar(); int getChar(); int getBlock(char *blk, int size); @@ -282,11 +293,11 @@ private: class FileStream: public BaseStream { public: - FileStream(FILE *fA, Guint startA, GBool limitedA, - Guint lengthA, Object *dictA); + FileStream(FILE *fA, GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA); virtual ~FileStream(); - virtual Stream *makeSubStream(Guint startA, GBool limitedA, - Guint lengthA, Object *dictA); + virtual Stream *makeSubStream(GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA); virtual StreamKind getKind() { return strFile; } virtual void reset(); virtual void close(); @@ -295,9 +306,9 @@ public: virtual int lookChar() { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); } virtual int getBlock(char *blk, int size); - virtual int getPos() { return bufPos + (int)(bufPtr - buf); } - virtual void setPos(Guint pos, int dir = 0); - virtual Guint getStart() { return start; } + virtual GFileOffset getPos() { return bufPos + (int)(bufPtr - buf); } + virtual void setPos(GFileOffset pos, int dir = 0); + virtual GFileOffset getStart() { return start; } virtual void moveStart(int delta); private: @@ -305,14 +316,14 @@ private: GBool fillBuf(); FILE *f; - Guint start; + GFileOffset start; GBool limited; - Guint length; + GFileOffset length; char buf[fileStreamBufSize]; char *bufPtr; char *bufEnd; - Guint bufPos; - int savePos; + GFileOffset bufPos; + GFileOffset savePos; GBool saved; }; @@ -325,8 +336,8 @@ public: MemStream(char *bufA, Guint startA, Guint lengthA, Object *dictA); virtual ~MemStream(); - virtual Stream *makeSubStream(Guint start, GBool limited, - Guint lengthA, Object *dictA); + virtual Stream *makeSubStream(GFileOffset start, GBool limited, + GFileOffset lengthA, Object *dictA); virtual StreamKind getKind() { return strWeird; } virtual void reset(); virtual void close(); @@ -335,9 +346,9 @@ public: virtual int lookChar() { return (bufPtr < bufEnd) ? (*bufPtr & 0xff) : EOF; } virtual int getBlock(char *blk, int size); - virtual int getPos() { return (int)(bufPtr - buf); } - virtual void setPos(Guint pos, int dir = 0); - virtual Guint getStart() { return start; } + virtual GFileOffset getPos() { return (GFileOffset)(bufPtr - buf); } + virtual void setPos(GFileOffset pos, int dir = 0); + virtual GFileOffset getStart() { return start; } virtual void moveStart(int delta); private: @@ -363,25 +374,25 @@ private: class EmbedStream: public BaseStream { public: - EmbedStream(Stream *strA, Object *dictA, GBool limitedA, Guint lengthA); + EmbedStream(Stream *strA, Object *dictA, GBool limitedA, GFileOffset lengthA); virtual ~EmbedStream(); - virtual Stream *makeSubStream(Guint start, GBool limitedA, - Guint lengthA, Object *dictA); + virtual Stream *makeSubStream(GFileOffset start, GBool limitedA, + GFileOffset lengthA, Object *dictA); virtual StreamKind getKind() { return str->getKind(); } virtual void reset() {} virtual int getChar(); virtual int lookChar(); virtual int getBlock(char *blk, int size); - virtual int getPos() { return str->getPos(); } - virtual void setPos(Guint pos, int dir = 0); - virtual Guint getStart(); + virtual GFileOffset getPos() { return str->getPos(); } + virtual void setPos(GFileOffset pos, int dir = 0); + virtual GFileOffset getStart(); virtual void moveStart(int delta); private: Stream *str; GBool limited; - Guint length; + GFileOffset length; }; //------------------------------------------------------------------------ @@ -623,9 +634,11 @@ private: DCTHuffTable acHuffTables[4]; // AC Huffman tables int numDCHuffTables; // number of DC Huffman tables int numACHuffTables; // number of AC Huffman tables - Guchar *rowBuf[4][32]; // buffer for one MCU (non-progressive mode) + Guchar *rowBuf; + Guchar *rowBufPtr; // current position within rowBuf + Guchar *rowBufEnd; // end of valid data in rowBuf int *frameBuf[4]; // buffer for frame (progressive mode) - int comp, x, y, dy; // current position within image/MCU + int comp, x, y; // current position within image/MCU int restartCtr; // MCUs left until restart int restartMarker; // next restart marker int eobRun; // number of EOBs left in the current run @@ -902,4 +915,42 @@ private: GBool fillBuf(); }; +//------------------------------------------------------------------------ +// LZWEncoder +//------------------------------------------------------------------------ + +struct LZWEncoderNode { + int byte; + LZWEncoderNode *next; // next sibling + LZWEncoderNode *children; // first child +}; + +class LZWEncoder: public FilterStream { +public: + + LZWEncoder(Stream *strA); + virtual ~LZWEncoder(); + virtual StreamKind getKind() { return strWeird; } + virtual void reset(); + virtual int getChar(); + virtual int lookChar(); + virtual GString *getPSFilter(int psLevel, const char *indent) + { return NULL; } + virtual GBool isBinary(GBool last = gTrue) { return gTrue; } + virtual GBool isEncoder() { return gTrue; } + +private: + + LZWEncoderNode table[4096]; + int nextSeq; + int codeLen; + Guchar inBuf[4096]; + int inBufLen; + int outBuf; + int outBufLen; + GBool needEOD; + + void fillBuf(); +}; + #endif diff --git a/xpdf/TextOutputDev.cc b/xpdf/TextOutputDev.cc index 971a3fe..be1fad2 100644 --- a/xpdf/TextOutputDev.cc +++ b/xpdf/TextOutputDev.cc @@ -2,7 +2,7 @@ // // TextOutputDev.cc // -// Copyright 1997-2003 Glyph & Cog, LLC +// Copyright 1997-2014 Glyph & Cog, LLC // //======================================================================== @@ -17,7 +17,7 @@ #include <stddef.h> #include <math.h> #include <ctype.h> -#ifdef WIN32 +#ifdef _WIN32 #include <fcntl.h> // for O_BINARY #include <io.h> // for setmode #endif @@ -33,107 +33,342 @@ #include "Link.h" #include "TextOutputDev.h" -#ifdef MACOS -// needed for setting type/creator of MacOS files -#include "ICSupport.h" -#endif - //------------------------------------------------------------------------ // parameters //------------------------------------------------------------------------ -// Each bucket in a text pool includes baselines within a range of -// this many points. -#define textPoolStep 4 +// Size of bins used for horizontal and vertical profiles is +// splitPrecisionMul * minFontSize. +#define splitPrecisionMul 0.05 + +// Minimum allowed split precision. +#define minSplitPrecision 0.01 + +// yMin and yMax (or xMin and xMax for rot=1,3) are adjusted by this +// fraction of the text height, to allow for slightly overlapping +// lines (or large ascent/descent values). +#define ascentAdjustFactor 0 +#define descentAdjustFactor 0.35 + +// Gaps larger than max{gap} - splitGapSlack * avgFontSize are +// considered to be equivalent. +#define splitGapSlack 0.2 + +// The vertical gap threshold (minimum gap required to split +// vertically) depends on the (approximate) number of lines in the +// block: +// threshold = (max + slope * nLines) * avgFontSize +// with a min value of vertGapThresholdMin * avgFontSize. +#define vertGapThresholdMin 0.8 +#define vertGapThresholdMax 3 +#define vertGapThresholdSlope -0.5 + +// Vertical gap threshold for table mode. +#define vertGapThresholdTableMin 0.2 +#define vertGapThresholdTableMax 0.5 +#define vertGapThresholdTableSlope -0.02 + +// A large character has a font size larger than +// largeCharThreshold * avgFontSize. +#define largeCharThreshold 1.5 + +// A block will be split vertically only if the resulting chunk +// widths are greater than vertSplitChunkThreshold * avgFontSize. +#define vertSplitChunkThreshold 2 -// Inter-character space width which will cause addChar to start a new -// word. -#define minWordBreakSpace 0.1 +// Max difference in primary,secondary coordinates (as a fraction of +// the font size) allowed for duplicated text (fake boldface, drop +// shadows) which is to be discarded. +#define dupMaxPriDelta 0.1 +#define dupMaxSecDelta 0.2 -// Negative inter-character space width, i.e., overlap, which will -// cause addChar to start a new word. -#define minDupBreakOverlap 0.2 +// Inter-character spacing that varies by less than this multiple of +// font size is assumed to be equivalent. +#define uniformSpacing 0.07 -// Max distance between baselines of two lines within a block, as a -// fraction of the font size. -#define maxLineSpacingDelta 1.5 +// Typical word spacing, as a fraction of font size. This will be +// added to the minimum inter-character spacing, to account for wide +// character spacing. +#define wordSpacing 0.1 -// Max difference in primary font sizes on two lines in the same -// block. Delta1 is used when examining new lines above and below the -// current block; delta2 is used when examining text that overlaps the -// current block; delta3 is used when examining text to the left and -// right of the current block. -#define maxBlockFontSizeDelta1 0.05 -#define maxBlockFontSizeDelta2 0.6 -#define maxBlockFontSizeDelta3 0.2 +// Minimum paragraph indent from left margin, as a fraction of font +// size. +#define minParagraphIndent 0.5 -// Max difference in font sizes inside a word. -#define maxWordFontSizeDelta 0.05 +// If the space between two lines is greater than +// paragraphSpacingThreshold * avgLineSpacing, start a new paragraph. +#define paragraphSpacingThreshold 1.2 -// Maximum distance between baselines of two words on the same line, -// e.g., distance between subscript or superscript and the primary -// baseline, as a fraction of the font size. -#define maxIntraLineDelta 0.5 +// If font size changes by at least this much (measured in points) +// between lines, start a new paragraph. +#define paragraphFontSizeDelta 1 -// Minimum inter-word spacing, as a fraction of the font size. (Only -// used for raw ordering.) -#define minWordSpacing 0.15 +// Spaces at the start of a line in physical layout mode are this wide +// (as a multiple of font size). +#define physLayoutSpaceWidth 0.33 -// Maximum inter-word spacing, as a fraction of the font size. -#define maxWordSpacing 1.5 +// Table cells (TextColumns) are allowed to overlap by this much +// in table layout mode (as a fraction of cell width or height). +#define tableCellOverlapSlack 0.05 -// Maximum horizontal spacing which will allow a word to be pulled -// into a block. -#define minColSpacing1 0.3 +// Primary axis delta which will cause a line break in raw mode +// (as a fraction of font size). +#define rawModeLineDelta 0.5 -// Minimum spacing between columns, as a fraction of the font size. -#define minColSpacing2 1.0 +// Secondary axis delta which will cause a word break in raw mode +// (as a fraction of font size). +#define rawModeWordSpacing 0.15 -// Maximum vertical spacing between blocks within a flow, as a -// multiple of the font size. -#define maxBlockSpacing 2.5 +// Secondary axis overlap which will cause a line break in raw mode +// (as a fraction of font size). +#define rawModeCharOverlap 0.2 -// Minimum spacing between characters within a word, as a fraction of -// the font size. -#define minCharSpacing -0.2 +// Max spacing (as a multiple of font size) allowed between the end of +// a line and a clipped character to be included in that line. +#define clippedTextMaxWordSpace 0.5 -// Maximum spacing between characters within a word, as a fraction of -// the font size, when there is no obvious extra-wide character -// spacing. -#define maxCharSpacing 0.03 +// Max width of underlines (in points). +#define maxUnderlineWidth 3 -// When extra-wide character spacing is detected, the inter-character -// space threshold is set to the minimum inter-character space -// multiplied by this constant. -#define maxWideCharSpacingMul 1.3 +// Max horizontal distance between edge of word and start of underline +// (as a fraction of font size). +#define underlineSlack 0.2 -// Upper limit on spacing between characters in a word. -#define maxWideCharSpacing 0.4 +// Max vertical distance between baseline of word and start of +// underline (as a fraction of font size). +#define underlineBaselineSlack 0.2 -// Max difference in primary,secondary coordinates (as a fraction of -// the font size) allowed for duplicated text (fake boldface, drop -// shadows) which is to be discarded. -#define dupMaxPriDelta 0.1 -#define dupMaxSecDelta 0.2 +// Max distance between edge of text and edge of link border (as a +// fraction of font size). +#define hyperlinkSlack 0.2 -// Max width of underlines (in points). -#define maxUnderlineWidth 3 +//------------------------------------------------------------------------ +// TextChar +//------------------------------------------------------------------------ -// Min distance between baseline and underline (in points). -//~ this should be font-size-dependent -#define minUnderlineGap -2 +class TextChar { +public: -// Max distance between baseline and underline (in points). -//~ this should be font-size-dependent -#define maxUnderlineGap 4 + TextChar(Unicode cA, int charPosA, int charLenA, + double xMinA, double yMinA, double xMaxA, double yMaxA, + int rotA, GBool clippedA, GBool invisibleA, + TextFontInfo *fontA, double fontSizeA, + double colorRA, double colorGA, double colorBA); + + static int cmpX(const void *p1, const void *p2); + static int cmpY(const void *p1, const void *p2); + + Unicode c; + int charPos; + int charLen; + double xMin, yMin, xMax, yMax; + Guchar rot; + char clipped; + char invisible; + TextFontInfo *font; + double fontSize; + double colorR, + colorG, + colorB; +}; -// Max horizontal distance between edge of word and start of underline -// (in points). -//~ this should be font-size-dependent -#define underlineSlack 1 +TextChar::TextChar(Unicode cA, int charPosA, int charLenA, + double xMinA, double yMinA, double xMaxA, double yMaxA, + int rotA, GBool clippedA, GBool invisibleA, + TextFontInfo *fontA, double fontSizeA, + double colorRA, double colorGA, double colorBA) { + double t; + + c = cA; + charPos = charPosA; + charLen = charLenA; + xMin = xMinA; + yMin = yMinA; + xMax = xMaxA; + yMax = yMaxA; + // this can happen with vertical writing mode, or with odd values + // for the char/word spacing parameters + if (xMin > xMax) { + t = xMin; xMin = xMax; xMax = t; + } + if (yMin > yMax) { + t = yMin; yMin = yMax; yMax = t; + } + rot = (Guchar)rotA; + clipped = (char)clippedA; + invisible = (char)invisibleA; + font = fontA; + fontSize = fontSizeA; + colorR = colorRA; + colorG = colorGA; + colorB = colorBA; +} + +int TextChar::cmpX(const void *p1, const void *p2) { + const TextChar *ch1 = *(const TextChar **)p1; + const TextChar *ch2 = *(const TextChar **)p2; + + if (ch1->xMin < ch2->xMin) { + return -1; + } else if (ch1->xMin > ch2->xMin) { + return 1; + } else { + return 0; + } +} + +int TextChar::cmpY(const void *p1, const void *p2) { + const TextChar *ch1 = *(const TextChar **)p1; + const TextChar *ch2 = *(const TextChar **)p2; + + if (ch1->yMin < ch2->yMin) { + return -1; + } else if (ch1->yMin > ch2->yMin) { + return 1; + } else { + return 0; + } +} + +//------------------------------------------------------------------------ +// TextBlock +//------------------------------------------------------------------------ + +enum TextBlockType { + blkVertSplit, + blkHorizSplit, + blkLeaf +}; + +enum TextBlockTag { + blkTagMulticolumn, + blkTagColumn, + blkTagLine +}; + +class TextBlock { +public: + + TextBlock(TextBlockType typeA, int rotA); + ~TextBlock(); + void addChild(TextBlock *child); + void addChild(TextChar *child); + void prependChild(TextChar *child); + void updateBounds(int childIdx); + + TextBlockType type; + TextBlockTag tag; + int rot; + double xMin, yMin, xMax, yMax; + GBool smallSplit; // true for blkVertSplit/blkHorizSplit + // where the gap size is small + GList *children; // for blkLeaf, children are TextWord; + // for others, children are TextBlock +}; + +TextBlock::TextBlock(TextBlockType typeA, int rotA) { + type = typeA; + tag = blkTagMulticolumn; + rot = rotA; + xMin = yMin = xMax = yMax = 0; + smallSplit = gFalse; + children = new GList(); +} + +TextBlock::~TextBlock() { + if (type == blkLeaf) { + delete children; + } else { + deleteGList(children, TextBlock); + } +} -// Max distance between edge of text and edge of link border -#define hyperlinkSlack 2 +void TextBlock::addChild(TextBlock *child) { + if (children->getLength() == 0) { + xMin = child->xMin; + yMin = child->yMin; + xMax = child->xMax; + yMax = child->yMax; + } else { + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } + } + children->append(child); +} + +void TextBlock::addChild(TextChar *child) { + if (children->getLength() == 0) { + xMin = child->xMin; + yMin = child->yMin; + xMax = child->xMax; + yMax = child->yMax; + } else { + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } + } + children->append(child); +} + +void TextBlock::prependChild(TextChar *child) { + if (children->getLength() == 0) { + xMin = child->xMin; + yMin = child->yMin; + xMax = child->xMax; + yMax = child->yMax; + } else { + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } + } + children->insert(0, child); +} + +void TextBlock::updateBounds(int childIdx) { + TextBlock *child; + + child = (TextBlock *)children->get(childIdx); + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } +} //------------------------------------------------------------------------ // TextUnderline @@ -157,159 +392,202 @@ public: class TextLink { public: - TextLink(int xMinA, int yMinA, int xMaxA, int yMaxA, Link *linkA) - { xMin = xMinA; yMin = yMinA; xMax = xMaxA; yMax = yMaxA; link = linkA; } - ~TextLink() {} + TextLink(double xMinA, double yMinA, double xMaxA, double yMaxA, + GString *uriA) + { xMin = xMinA; yMin = yMinA; xMax = xMaxA; yMax = yMaxA; uri = uriA; } + ~TextLink(); - int xMin, yMin, xMax, yMax; - Link *link; + double xMin, yMin, xMax, yMax; + GString *uri; }; +TextLink::~TextLink() { + if (uri) { + delete uri; + } +} + +//------------------------------------------------------------------------ +// TextOutputControl +//------------------------------------------------------------------------ + +TextOutputControl::TextOutputControl() { + mode = textOutReadingOrder; + fixedPitch = 0; + fixedLineSpacing = 0; + html = gFalse; + clipText = gFalse; +} + + //------------------------------------------------------------------------ // TextFontInfo //------------------------------------------------------------------------ TextFontInfo::TextFontInfo(GfxState *state) { + GfxFont *gfxFont; + gfxFont = state->getFont(); -#if TEXTOUT_WORD_LIST + if (gfxFont) { + fontID = *gfxFont->getID(); + ascent = gfxFont->getAscent(); + descent = gfxFont->getDescent(); + // "odd" ascent/descent values cause trouble more often than not + // (in theory these could be legitimate values for oddly designed + // fonts -- but they are more often due to buggy PDF generators) + // (values that are too small are a different issue -- those seem + // to be more commonly legitimate) + if (ascent > 1) { + ascent = 0.75; + } + if (descent < -0.5) { + descent = -0.25; + } + } else { + fontID.num = -1; + fontID.gen = -1; + ascent = 0.75; + descent = -0.25; + } fontName = (gfxFont && gfxFont->getName()) ? gfxFont->getName()->copy() : (GString *)NULL; flags = gfxFont ? gfxFont->getFlags() : 0; -#endif + mWidth = 0; + if (gfxFont && !gfxFont->isCIDFont()) { + char *name; + int code; + for (code = 0; code < 256; ++code) { + if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && + name[0] == 'm' && name[1] == '\0') { + mWidth = ((Gfx8BitFont *)gfxFont)->getWidth(code); + break; + } + } + } } TextFontInfo::~TextFontInfo() { -#if TEXTOUT_WORD_LIST if (fontName) { delete fontName; } -#endif } GBool TextFontInfo::matches(GfxState *state) { - return state->getFont() == gfxFont; + Ref *id; + + if (!state->getFont()) { + return gFalse; + } + id = state->getFont()->getID(); + return id->num == fontID.num && id->gen == fontID.gen; } //------------------------------------------------------------------------ // TextWord //------------------------------------------------------------------------ -TextWord::TextWord(GfxState *state, int rotA, double x0, double y0, - TextFontInfo *fontA, double fontSizeA) { - GfxFont *gfxFont; - double x, y, ascent, descent; - int wMode; +// Build a TextWord object, using chars[start .. start+len-1]. +// (If rot >= 2, the chars list is in reverse order.) +TextWord::TextWord(GList *chars, int start, int lenA, + int rotA, GBool spaceAfterA) { + TextChar *ch; + int i; rot = rotA; - font = fontA; - fontSize = fontSizeA; - state->transform(x0, y0, &x, &y); - if ((gfxFont = font->gfxFont)) { - ascent = gfxFont->getAscent() * fontSize; - descent = gfxFont->getDescent() * fontSize; - wMode = gfxFont->getWMode(); - } else { - // this means that the PDF file draws text without a current font, - // which should never happen - ascent = 0.95 * fontSize; - descent = -0.35 * fontSize; - wMode = 0; - } - if (wMode) { // vertical writing mode - // NB: the rotation value has been incremented by 1 (in - // TextPage::beginWord()) for vertical writing mode - switch (rot) { - case 0: - yMin = y - fontSize; - yMax = y; - base = y; - break; - case 1: - xMin = x; - xMax = x + fontSize; - base = x; - break; - case 2: - yMin = y; - yMax = y + fontSize; - base = y; - break; - case 3: - xMin = x - fontSize; - xMax = x; - base = x; - break; + len = lenA; + text = (Unicode *)gmallocn(len, sizeof(Unicode)); + edge = (double *)gmallocn(len + 1, sizeof(double)); + charPos = (int *)gmallocn(len + 1, sizeof(int)); + switch (rot) { + case 0: + default: + ch = (TextChar *)chars->get(start); + xMin = ch->xMin; + yMin = ch->yMin; + yMax = ch->yMax; + ch = (TextChar *)chars->get(start + len - 1); + xMax = ch->xMax; + break; + case 1: + ch = (TextChar *)chars->get(start); + xMin = ch->xMin; + xMax = ch->xMax; + yMin = ch->yMin; + ch = (TextChar *)chars->get(start + len - 1); + yMax = ch->yMax; + break; + case 2: + ch = (TextChar *)chars->get(start); + xMax = ch->xMax; + yMin = ch->yMin; + yMax = ch->yMax; + ch = (TextChar *)chars->get(start + len - 1); + xMin = ch->xMin; + break; + case 3: + ch = (TextChar *)chars->get(start); + xMin = ch->xMin; + xMax = ch->xMax; + yMax = ch->yMax; + ch = (TextChar *)chars->get(start + len - 1); + yMin = ch->yMin; + break; + } + for (i = 0; i < len; ++i) { + ch = (TextChar *)chars->get(rot >= 2 ? start + len - 1 - i : start + i); + text[i] = ch->c; + charPos[i] = ch->charPos; + if (i == len - 1) { + charPos[len] = ch->charPos + ch->charLen; } - } else { // horizontal writing mode switch (rot) { case 0: - yMin = y - ascent; - yMax = y - descent; - if (yMin == yMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - yMin = y; - yMax = y + 1; + default: + edge[i] = ch->xMin; + if (i == len - 1) { + edge[len] = ch->xMax; } - base = y; break; case 1: - xMin = x + descent; - xMax = x + ascent; - if (xMin == xMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - xMin = x; - xMax = x + 1; + edge[i] = ch->yMin; + if (i == len - 1) { + edge[len] = ch->yMax; } - base = x; break; case 2: - yMin = y + descent; - yMax = y + ascent; - if (yMin == yMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - yMin = y; - yMax = y + 1; + edge[i] = ch->xMax; + if (i == len - 1) { + edge[len] = ch->xMin; } - base = y; break; case 3: - xMin = x - ascent; - xMax = x - descent; - if (xMin == xMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - xMin = x; - xMax = x + 1; + edge[i] = ch->yMax; + if (i == len - 1) { + edge[len] = ch->yMin; } - base = x; break; } } - text = NULL; - edge = NULL; - charPos = NULL; - len = size = 0; - spaceAfter = gFalse; - next = NULL; - -#if TEXTOUT_WORD_LIST - GfxRGB rgb; - - if ((state->getRender() & 3) == 1) { - state->getStrokeRGB(&rgb); - } else { - state->getFillRGB(&rgb); - } - colorR = colToDbl(rgb.r); - colorG = colToDbl(rgb.g); - colorB = colToDbl(rgb.b); -#endif - + ch = (TextChar *)chars->get(start); + font = ch->font; + fontSize = ch->fontSize; + spaceAfter = spaceAfterA; underlined = gFalse; link = NULL; + colorR = ch->colorR; + colorG = ch->colorG; + colorB = ch->colorB; + invisible = ch->invisible; +} + +TextWord::TextWord(TextWord *word) { + *this = *word; + text = (Unicode *)gmallocn(len, sizeof(Unicode)); + memcpy(text, word->text, len * sizeof(Unicode)); + edge = (double *)gmallocn(len + 1, sizeof(double)); + memcpy(edge, word->edge, (len + 1) * sizeof(double)); + charPos = (int *)gmallocn(len + 1, sizeof(int)); + memcpy(charPos, word->charPos, (len + 1) * sizeof(int)); } TextWord::~TextWord() { @@ -318,175 +596,65 @@ TextWord::~TextWord() { gfree(charPos); } -void TextWord::addChar(GfxState *state, double x, double y, - double dx, double dy, int charPosA, int charLen, - Unicode u) { - int wMode; - - if (len == size) { - size += 16; - text = (Unicode *)greallocn(text, size, sizeof(Unicode)); - edge = (double *)greallocn(edge, size + 1, sizeof(double)); - charPos = (int *)greallocn(charPos, size + 1, sizeof(int)); - } - text[len] = u; - charPos[len] = charPosA; - charPos[len + 1] = charPosA + charLen; - wMode = font->gfxFont ? font->gfxFont->getWMode() : 0; - if (wMode) { // vertical writing mode - // NB: the rotation value has been incremented by 1 (in - // TextPage::beginWord()) for vertical writing mode - switch (rot) { - case 0: - if (len == 0) { - xMin = x - fontSize; - } - edge[len] = x - fontSize; - xMax = edge[len+1] = x; - break; - case 1: - if (len == 0) { - yMin = y - fontSize; - } - edge[len] = y - fontSize; - yMax = edge[len+1] = y; - break; - case 2: - if (len == 0) { - xMax = x + fontSize; - } - edge[len] = x + fontSize; - xMin = edge[len+1] = x; - break; - case 3: - if (len == 0) { - yMax = y + fontSize; - } - edge[len] = y + fontSize; - yMin = edge[len+1] = y; - break; - } - } else { // horizontal writing mode - switch (rot) { - case 0: - if (len == 0) { - xMin = x; - } - edge[len] = x; - xMax = edge[len+1] = x + dx; - break; - case 1: - if (len == 0) { - yMin = y; - } - edge[len] = y; - yMax = edge[len+1] = y + dy; - break; - case 2: - if (len == 0) { - xMax = x; - } - edge[len] = x; - xMin = edge[len+1] = x + dx; - break; - case 3: - if (len == 0) { - yMax = y; - } - edge[len] = y; - yMin = edge[len+1] = y + dy; - break; - } +// This is used to append a clipped character to a word. +void TextWord::appendChar(TextChar *ch) { + if (ch->xMin < xMin) { + xMin = ch->xMin; } - ++len; -} - -void TextWord::merge(TextWord *word) { - int i; - - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMax > yMax) { - yMax = word->yMax; + if (ch->xMax > xMax) { + xMax = ch->xMax; } - if (len + word->len > size) { - size = len + word->len; - text = (Unicode *)greallocn(text, size, sizeof(Unicode)); - edge = (double *)greallocn(edge, size + 1, sizeof(double)); - charPos = (int *)greallocn(charPos, size + 1, sizeof(int)); + if (ch->yMin < yMin) { + yMin = ch->yMin; } - for (i = 0; i < word->len; ++i) { - text[len + i] = word->text[i]; - edge[len + i] = word->edge[i]; - charPos[len + i] = word->charPos[i]; + if (ch->yMax > yMax) { + yMax = ch->yMax; } - edge[len + word->len] = word->edge[word->len]; - charPos[len + word->len] = word->charPos[word->len]; - len += word->len; -} - -inline int TextWord::primaryCmp(TextWord *word) { - double cmp; - - cmp = 0; // make gcc happy + text = (Unicode *)greallocn(text, len + 1, sizeof(Unicode)); + edge = (double *)greallocn(edge, len + 2, sizeof(double)); + charPos = (int *)greallocn(charPos, len + 2, sizeof(int)); + text[len] = ch->c; + charPos[len] = ch->charPos; + charPos[len+1] = ch->charPos + ch->charLen; switch (rot) { case 0: - cmp = xMin - word->xMin; + default: + edge[len] = ch->xMin; + edge[len+1] = ch->xMax; break; case 1: - cmp = yMin - word->yMin; + edge[len] = ch->yMin; + edge[len+1] = ch->yMax; break; case 2: - cmp = word->xMax - xMax; + edge[len] = ch->xMax; + edge[len+1] = ch->xMin; break; case 3: - cmp = word->yMax - yMax; + edge[len] = ch->yMax; + edge[len+1] = ch->yMin; break; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -double TextWord::primaryDelta(TextWord *word) { - double delta; - - delta = 0; // make gcc happy - switch (rot) { - case 0: - delta = word->xMin - xMax; - break; - case 1: - delta = word->yMin - yMax; - break; - case 2: - delta = xMin - word->xMax; - break; - case 3: - delta = yMin - word->yMax; - break; - } - return delta; + ++len; } int TextWord::cmpYX(const void *p1, const void *p2) { - TextWord *word1 = *(TextWord **)p1; - TextWord *word2 = *(TextWord **)p2; + const TextWord *word1 = *(const TextWord **)p1; + const TextWord *word2 = *(const TextWord **)p2; double cmp; - cmp = word1->yMin - word2->yMin; - if (cmp == 0) { + if ((cmp = word1->yMin - word2->yMin) == 0) { cmp = word1->xMin - word2->xMin; } return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; } -#if TEXTOUT_WORD_LIST +int TextWord::cmpCharPos(const void *p1, const void *p2) { + const TextWord *word1 = *(const TextWord **)p1; + const TextWord *word2 = *(const TextWord **)p2; + + return word1->charPos[0] - word2->charPos[0]; +} GString *TextWord::getText() { GString *s; @@ -539,1285 +707,198 @@ void TextWord::getCharBBox(int charIdx, double *xMinA, double *yMinA, } } -#endif // TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextPool -//------------------------------------------------------------------------ - -TextPool::TextPool() { - minBaseIdx = 0; - maxBaseIdx = -1; - pool = NULL; - cursor = NULL; - cursorBaseIdx = -1; -} - -TextPool::~TextPool() { - int baseIdx; - TextWord *word, *word2; - - for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) { - for (word = pool[baseIdx - minBaseIdx]; word; word = word2) { - word2 = word->next; - delete word; - } - } - gfree(pool); -} - -int TextPool::getBaseIdx(double base) { - int baseIdx; - - baseIdx = (int)(base / textPoolStep); - if (baseIdx < minBaseIdx) { - return minBaseIdx; - } - if (baseIdx > maxBaseIdx) { - return maxBaseIdx; - } - return baseIdx; -} - -void TextPool::addWord(TextWord *word) { - TextWord **newPool; - int wordBaseIdx, newMinBaseIdx, newMaxBaseIdx, baseIdx; - TextWord *w0, *w1; - - // expand the array if needed - wordBaseIdx = (int)(word->base / textPoolStep); - if (minBaseIdx > maxBaseIdx) { - minBaseIdx = wordBaseIdx - 128; - maxBaseIdx = wordBaseIdx + 128; - pool = (TextWord **)gmallocn(maxBaseIdx - minBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) { - pool[baseIdx - minBaseIdx] = NULL; - } - } else if (wordBaseIdx < minBaseIdx) { - newMinBaseIdx = wordBaseIdx - 128; - newPool = (TextWord **)gmallocn(maxBaseIdx - newMinBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = newMinBaseIdx; baseIdx < minBaseIdx; ++baseIdx) { - newPool[baseIdx - newMinBaseIdx] = NULL; - } - memcpy(&newPool[minBaseIdx - newMinBaseIdx], pool, - (maxBaseIdx - minBaseIdx + 1) * sizeof(TextWord *)); - gfree(pool); - pool = newPool; - minBaseIdx = newMinBaseIdx; - } else if (wordBaseIdx > maxBaseIdx) { - newMaxBaseIdx = wordBaseIdx + 128; - pool = (TextWord **)greallocn(pool, newMaxBaseIdx - minBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = maxBaseIdx + 1; baseIdx <= newMaxBaseIdx; ++baseIdx) { - pool[baseIdx - minBaseIdx] = NULL; - } - maxBaseIdx = newMaxBaseIdx; - } - - // insert the new word - if (cursor && wordBaseIdx == cursorBaseIdx && - word->primaryCmp(cursor) >= 0) { - w0 = cursor; - w1 = cursor->next; - } else { - w0 = NULL; - w1 = pool[wordBaseIdx - minBaseIdx]; - } - for (; w1 && word->primaryCmp(w1) > 0; w0 = w1, w1 = w1->next) ; - word->next = w1; - if (w0) { - w0->next = word; - } else { - pool[wordBaseIdx - minBaseIdx] = word; - } - cursor = word; - cursorBaseIdx = wordBaseIdx; -} - -//------------------------------------------------------------------------ -// TextLine -//------------------------------------------------------------------------ - -TextLine::TextLine(TextBlock *blkA, int rotA, double baseA) { - blk = blkA; - rot = rotA; - xMin = yMin = 0; - xMax = yMax = -1; - base = baseA; - words = lastWord = NULL; - text = NULL; - edge = NULL; - col = NULL; - len = 0; - convertedLen = 0; - hyphenated = gFalse; - next = NULL; -} - -TextLine::~TextLine() { - TextWord *word; - - while (words) { - word = words; - words = words->next; - delete word; - } - gfree(text); - gfree(edge); - gfree(col); -} - -void TextLine::addWord(TextWord *word) { - if (lastWord) { - lastWord->next = word; - } else { - words = word; - } - lastWord = word; - - if (xMin > xMax) { - xMin = word->xMin; - xMax = word->xMax; - yMin = word->yMin; - yMax = word->yMax; - } else { - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->yMax > yMax) { - yMax = word->yMax; - } - } -} - -double TextLine::primaryDelta(TextLine *line) { - double delta; - - delta = 0; // make gcc happy - switch (rot) { - case 0: - delta = line->xMin - xMax; - break; - case 1: - delta = line->yMin - yMax; - break; - case 2: - delta = xMin - line->xMax; - break; - case 3: - delta = yMin - line->yMax; - break; - } - return delta; -} - -int TextLine::primaryCmp(TextLine *line) { - double cmp; - - cmp = 0; // make gcc happy +double TextWord::getBaseline() { switch (rot) { case 0: - cmp = xMin - line->xMin; - break; + default: + return yMax + fontSize * font->descent; case 1: - cmp = yMin - line->yMin; - break; + return xMin - fontSize * font->descent; case 2: - cmp = line->xMax - xMax; - break; + return yMin - fontSize * font->descent; case 3: - cmp = line->yMax - yMax; - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLine::secondaryCmp(TextLine *line) { - double cmp; - - cmp = (rot == 0 || rot == 3) ? base - line->base : line->base - base; - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLine::cmpYX(TextLine *line) { - int cmp; - - if ((cmp = secondaryCmp(line))) { - return cmp; + return xMax + fontSize * font->descent; } - return primaryCmp(line); } -int TextLine::cmpXY(const void *p1, const void *p2) { - TextLine *line1 = *(TextLine **)p1; - TextLine *line2 = *(TextLine **)p2; - int cmp; - - if ((cmp = line1->primaryCmp(line2))) { - return cmp; - } - return line1->secondaryCmp(line2); +GString *TextWord::getLinkURI() { + return link ? link->uri : (GString *)NULL; } -void TextLine::coalesce(UnicodeMap *uMap) { - TextWord *word0, *word1; - double space, delta, minSpace; - GBool isUnicode; - char buf[8]; - int i, j; - - if (words->next) { +//------------------------------------------------------------------------ +// TextLine +//------------------------------------------------------------------------ - // compute the inter-word space threshold - if (words->len > 1 || words->next->len > 1) { - minSpace = 0; - } else { - minSpace = words->primaryDelta(words->next); - for (word0 = words->next, word1 = word0->next; - word1 && minSpace > 0; - word0 = word1, word1 = word0->next) { - if (word1->len > 1) { - minSpace = 0; - } - delta = word0->primaryDelta(word1); - if (delta < minSpace) { - minSpace = delta; - } - } - } - if (minSpace <= 0) { - space = maxCharSpacing * words->fontSize; - } else { - space = maxWideCharSpacingMul * minSpace; - if (space > maxWideCharSpacing * words->fontSize) { - space = maxWideCharSpacing * words->fontSize; - } - } +TextLine::TextLine(GList *wordsA, double xMinA, double yMinA, + double xMaxA, double yMaxA, double fontSizeA) { + TextWord *word; + int i, j, k; - // merge words - word0 = words; - word1 = words->next; - while (word1) { - if (word0->primaryDelta(word1) >= space) { - word0->spaceAfter = gTrue; - word0 = word1; - word1 = word1->next; - } else if (word0->font == word1->font && - word0->underlined == word1->underlined && - fabs(word0->fontSize - word1->fontSize) < - maxWordFontSizeDelta * words->fontSize && - word1->charPos[0] == word0->charPos[word0->len]) { - word0->merge(word1); - word0->next = word1->next; - delete word1; - word1 = word0->next; - } else { - word0 = word1; - word1 = word1->next; - } - } - } + words = wordsA; + rot = 0; + xMin = xMinA; + yMin = yMinA; + xMax = xMaxA; + yMax = yMaxA; + fontSize = fontSizeA; + px = 0; + pw = 0; - // build the line text - isUnicode = uMap ? uMap->isUnicode() : gFalse; + // build the text len = 0; - for (word1 = words; word1; word1 = word1->next) { - len += word1->len; - if (word1->spaceAfter) { + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + len += word->len; + if (word->spaceAfter) { ++len; } } text = (Unicode *)gmallocn(len, sizeof(Unicode)); edge = (double *)gmallocn(len + 1, sizeof(double)); - i = 0; - for (word1 = words; word1; word1 = word1->next) { - for (j = 0; j < word1->len; ++j) { - text[i] = word1->text[j]; - edge[i] = word1->edge[j]; - ++i; + j = 0; + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + if (i == 0) { + rot = word->rot; } - edge[i] = word1->edge[word1->len]; - if (word1->spaceAfter) { - text[i] = (Unicode)0x0020; - ++i; + for (k = 0; k < word->len; ++k) { + text[j] = word->text[k]; + edge[j] = word->edge[k]; + ++j; } - } - - // compute convertedLen and set up the col array - col = (int *)gmallocn(len + 1, sizeof(int)); - convertedLen = 0; - for (i = 0; i < len; ++i) { - col[i] = convertedLen; - if (isUnicode) { - ++convertedLen; - } else if (uMap) { - convertedLen += uMap->mapUnicode(text[i], buf, sizeof(buf)); + edge[j] = word->edge[word->len]; + if (word->spaceAfter) { + text[j] = (Unicode)0x0020; + ++j; + edge[j] = edge[j - 1]; } } - col[len] = convertedLen; - - // check for hyphen at end of line - //~ need to check for other chars used as hyphens + //~ need to check for other Unicode chars used as hyphens hyphenated = text[len - 1] == (Unicode)'-'; } -//------------------------------------------------------------------------ -// TextLineFrag -//------------------------------------------------------------------------ - -class TextLineFrag { -public: - - TextLine *line; // the line object - int start, len; // offset and length of this fragment - // (in Unicode chars) - double xMin, xMax; // bounding box coordinates - double yMin, yMax; - double base; // baseline virtual coordinate - int col; // first column - - void init(TextLine *lineA, int startA, int lenA); - void computeCoords(GBool oneRot); - - static int cmpYXPrimaryRot(const void *p1, const void *p2); - static int cmpYXLineRot(const void *p1, const void *p2); - static int cmpXYLineRot(const void *p1, const void *p2); - static int cmpXYColumnPrimaryRot(const void *p1, const void *p2); - static int cmpXYColumnLineRot(const void *p1, const void *p2); -}; - -void TextLineFrag::init(TextLine *lineA, int startA, int lenA) { - line = lineA; - start = startA; - len = lenA; - col = line->col[start]; -} - -void TextLineFrag::computeCoords(GBool oneRot) { - TextBlock *blk; - double d0, d1, d2, d3, d4; - - if (oneRot) { - - switch (line->rot) { - case 0: - xMin = line->edge[start]; - xMax = line->edge[start + len]; - yMin = line->yMin; - yMax = line->yMax; - break; - case 1: - xMin = line->xMin; - xMax = line->xMax; - yMin = line->edge[start]; - yMax = line->edge[start + len]; - break; - case 2: - xMin = line->edge[start + len]; - xMax = line->edge[start]; - yMin = line->yMin; - yMax = line->yMax; - break; - case 3: - xMin = line->xMin; - xMax = line->xMax; - yMin = line->edge[start + len]; - yMax = line->edge[start]; - break; - } - base = line->base; - - } else { - - if (line->rot == 0 && line->blk->page->primaryRot == 0) { - - xMin = line->edge[start]; - xMax = line->edge[start + len]; - yMin = line->yMin; - yMax = line->yMax; - base = line->base; - - } else { - - blk = line->blk; - d0 = line->edge[start]; - d1 = line->edge[start + len]; - d2 = d3 = d4 = 0; // make gcc happy - - switch (line->rot) { - case 0: - d2 = line->yMin; - d3 = line->yMax; - d4 = line->base; - d0 = (d0 - blk->xMin) / (blk->xMax - blk->xMin); - d1 = (d1 - blk->xMin) / (blk->xMax - blk->xMin); - d2 = (d2 - blk->yMin) / (blk->yMax - blk->yMin); - d3 = (d3 - blk->yMin) / (blk->yMax - blk->yMin); - d4 = (d4 - blk->yMin) / (blk->yMax - blk->yMin); - break; - case 1: - d2 = line->xMax; - d3 = line->xMin; - d4 = line->base; - d0 = (d0 - blk->yMin) / (blk->yMax - blk->yMin); - d1 = (d1 - blk->yMin) / (blk->yMax - blk->yMin); - d2 = (blk->xMax - d2) / (blk->xMax - blk->xMin); - d3 = (blk->xMax - d3) / (blk->xMax - blk->xMin); - d4 = (blk->xMax - d4) / (blk->xMax - blk->xMin); - break; - case 2: - d2 = line->yMax; - d3 = line->yMin; - d4 = line->base; - d0 = (blk->xMax - d0) / (blk->xMax - blk->xMin); - d1 = (blk->xMax - d1) / (blk->xMax - blk->xMin); - d2 = (blk->yMax - d2) / (blk->yMax - blk->yMin); - d3 = (blk->yMax - d3) / (blk->yMax - blk->yMin); - d4 = (blk->yMax - d4) / (blk->yMax - blk->yMin); - break; - case 3: - d2 = line->xMin; - d3 = line->xMax; - d4 = line->base; - d0 = (blk->yMax - d0) / (blk->yMax - blk->yMin); - d1 = (blk->yMax - d1) / (blk->yMax - blk->yMin); - d2 = (d2 - blk->xMin) / (blk->xMax - blk->xMin); - d3 = (d3 - blk->xMin) / (blk->xMax - blk->xMin); - d4 = (d4 - blk->xMin) / (blk->xMax - blk->xMin); - break; - } - - switch (line->blk->page->primaryRot) { - case 0: - xMin = blk->xMin + d0 * (blk->xMax - blk->xMin); - xMax = blk->xMin + d1 * (blk->xMax - blk->xMin); - yMin = blk->yMin + d2 * (blk->yMax - blk->yMin); - yMax = blk->yMin + d3 * (blk->yMax - blk->yMin); - base = blk->yMin + d4 * (blk->yMax - blk->yMin); - break; - case 1: - xMin = blk->xMax - d3 * (blk->xMax - blk->xMin); - xMax = blk->xMax - d2 * (blk->xMax - blk->xMin); - yMin = blk->yMin + d0 * (blk->yMax - blk->yMin); - yMax = blk->yMin + d1 * (blk->yMax - blk->yMin); - base = blk->xMax - d4 * (blk->xMax - blk->xMin); - break; - case 2: - xMin = blk->xMax - d1 * (blk->xMax - blk->xMin); - xMax = blk->xMax - d0 * (blk->xMax - blk->xMin); - yMin = blk->yMax - d3 * (blk->yMax - blk->yMin); - yMax = blk->yMax - d2 * (blk->yMax - blk->yMin); - base = blk->yMax - d4 * (blk->yMax - blk->yMin); - break; - case 3: - xMin = blk->xMin + d2 * (blk->xMax - blk->xMin); - xMax = blk->xMin + d3 * (blk->xMax - blk->xMin); - yMin = blk->yMax - d1 * (blk->yMax - blk->yMin); - yMax = blk->yMax - d0 * (blk->yMax - blk->yMin); - base = blk->xMin + d4 * (blk->xMax - blk->xMin); - break; - } - - } - } -} - -int TextLineFrag::cmpYXPrimaryRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (frag1->line->blk->page->primaryRot) { - case 0: - if (fabs(cmp = frag1->yMin - frag2->yMin) < 0.01) { - cmp = frag1->xMin - frag2->xMin; - } - break; - case 1: - if (fabs(cmp = frag2->xMax - frag1->xMax) < 0.01) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 2: - if (fabs(cmp = frag2->yMin - frag1->yMin) < 0.01) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 3: - if (fabs(cmp = frag1->xMax - frag2->xMax) < 0.01) { - cmp = frag2->yMax - frag1->yMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLineFrag::cmpYXLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: - if ((cmp = frag1->yMin - frag2->yMin) == 0) { - cmp = frag1->xMin - frag2->xMin; - } - break; - case 1: - if ((cmp = frag2->xMax - frag1->xMax) == 0) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 2: - if ((cmp = frag2->yMin - frag1->yMin) == 0) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 3: - if ((cmp = frag1->xMax - frag2->xMax) == 0) { - cmp = frag2->yMax - frag1->yMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; +TextLine::~TextLine() { + deleteGList(words, TextWord); + gfree(text); + gfree(edge); } -int TextLineFrag::cmpXYLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; +double TextLine::getBaseline() { + TextWord *word0; - cmp = 0; // make gcc happy - switch (frag1->line->rot) { + word0 = (TextWord *)words->get(0); + switch (rot) { case 0: - if ((cmp = frag1->xMin - frag2->xMin) == 0) { - cmp = frag1->yMin - frag2->yMin; - } - break; + default: + return yMax + fontSize * word0->font->descent; case 1: - if ((cmp = frag1->yMin - frag2->yMin) == 0) { - cmp = frag2->xMax - frag1->xMax; - } - break; + return xMin - fontSize * word0->font->descent; case 2: - if ((cmp = frag2->xMax - frag1->xMax) == 0) { - cmp = frag2->yMin - frag1->yMin; - } - break; + return yMin - fontSize * word0->font->descent; case 3: - if ((cmp = frag2->yMax - frag1->yMax) == 0) { - cmp = frag1->xMax - frag2->xMax; - } - break; + return xMax + fontSize * word0->font->descent; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLineFrag::cmpXYColumnPrimaryRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - // if columns overlap, compare y values - if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] - - frag2->line->col[frag2->start]) && - frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start])) { - cmp = 0; // make gcc happy - switch (frag1->line->blk->page->primaryRot) { - case 0: cmp = frag1->yMin - frag2->yMin; break; - case 1: cmp = frag2->xMax - frag1->xMax; break; - case 2: cmp = frag2->yMin - frag1->yMin; break; - case 3: cmp = frag1->xMax - frag2->xMax; break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; - } - - // otherwise, compare starting column - return frag1->col - frag2->col; -} - -int TextLineFrag::cmpXYColumnLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - // if columns overlap, compare y values - if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] - - frag2->line->col[frag2->start]) && - frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start])) { - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: cmp = frag1->yMin - frag2->yMin; break; - case 1: cmp = frag2->xMax - frag1->xMax; break; - case 2: cmp = frag2->yMin - frag1->yMin; break; - case 3: cmp = frag1->xMax - frag2->xMax; break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; - } - - // otherwise, compare starting column - return frag1->col - frag2->col; } //------------------------------------------------------------------------ -// TextBlock +// TextParagraph //------------------------------------------------------------------------ -TextBlock::TextBlock(TextPage *pageA, int rotA) { - page = pageA; - rot = rotA; - xMin = yMin = 0; - xMax = yMax = -1; - priMin = 0; - priMax = page->pageWidth; - pool = new TextPool(); - lines = NULL; - curLine = NULL; - next = NULL; - stackNext = NULL; -} - -TextBlock::~TextBlock() { +TextParagraph::TextParagraph(GList *linesA) { TextLine *line; + int i; - delete pool; - while (lines) { - line = lines; - lines = lines->next; - delete line; - } -} - -void TextBlock::addWord(TextWord *word) { - pool->addWord(word); - if (xMin > xMax) { - xMin = word->xMin; - xMax = word->xMax; - yMin = word->yMin; - yMax = word->yMax; - } else { - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->yMax > yMax) { - yMax = word->yMax; - } - } -} - -void TextBlock::coalesce(UnicodeMap *uMap, double fixedPitch) { - TextWord *word0, *word1, *word2, *bestWord0, *bestWord1, *lastWord; - TextLine *line, *line0, *line1; - int poolMinBaseIdx, startBaseIdx, minBaseIdx, maxBaseIdx; - int baseIdx, bestWordBaseIdx, idx0, idx1; - double minBase, maxBase; - double fontSize, wordSpacing, delta, priDelta, secDelta; - TextLine **lineArray; - GBool found, overlap; - int col1, col2; - int i, j, k; - - // discard duplicated text (fake boldface, drop shadows) - for (idx0 = pool->minBaseIdx; idx0 <= pool->maxBaseIdx; ++idx0) { - word0 = pool->getPool(idx0); - while (word0) { - priDelta = dupMaxPriDelta * word0->fontSize; - secDelta = dupMaxSecDelta * word0->fontSize; - maxBaseIdx = pool->getBaseIdx(word0->base + secDelta); - found = gFalse; - word1 = word2 = NULL; // make gcc happy - for (idx1 = idx0; idx1 <= maxBaseIdx; ++idx1) { - if (idx1 == idx0) { - word1 = word0; - word2 = word0->next; - } else { - word1 = NULL; - word2 = pool->getPool(idx1); - } - for (; word2; word1 = word2, word2 = word2->next) { - if (word2->len == word0->len && - !memcmp(word2->text, word0->text, - word0->len * sizeof(Unicode))) { - switch (rot) { - case 0: - case 2: - found = fabs(word0->xMin - word2->xMin) < priDelta && - fabs(word0->xMax - word2->xMax) < priDelta && - fabs(word0->yMin - word2->yMin) < secDelta && - fabs(word0->yMax - word2->yMax) < secDelta; - break; - case 1: - case 3: - found = fabs(word0->xMin - word2->xMin) < secDelta && - fabs(word0->xMax - word2->xMax) < secDelta && - fabs(word0->yMin - word2->yMin) < priDelta && - fabs(word0->yMax - word2->yMax) < priDelta; - break; - } - } - if (found) { - break; - } - } - if (found) { - break; - } - } - if (found) { - if (word1) { - word1->next = word2->next; - } else { - pool->setPool(idx1, word2->next); - } - delete word2; - } else { - word0 = word0->next; - } - } - } - - // build the lines - curLine = NULL; - poolMinBaseIdx = pool->minBaseIdx; - charCount = 0; - nLines = 0; - while (1) { - - // find the first non-empty line in the pool - for (; - poolMinBaseIdx <= pool->maxBaseIdx && !pool->getPool(poolMinBaseIdx); - ++poolMinBaseIdx) ; - if (poolMinBaseIdx > pool->maxBaseIdx) { - break; - } - - // look for the left-most word in the first four lines of the - // pool -- this avoids starting with a superscript word - startBaseIdx = poolMinBaseIdx; - for (baseIdx = poolMinBaseIdx + 1; - baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx; - ++baseIdx) { - if (!pool->getPool(baseIdx)) { - continue; - } - if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx)) - < 0) { - startBaseIdx = baseIdx; - } - } - - // create a new line - word0 = pool->getPool(startBaseIdx); - pool->setPool(startBaseIdx, word0->next); - word0->next = NULL; - line = new TextLine(this, word0->rot, word0->base); - line->addWord(word0); - lastWord = word0; - - // compute the search range - fontSize = word0->fontSize; - minBase = word0->base - maxIntraLineDelta * fontSize; - maxBase = word0->base + maxIntraLineDelta * fontSize; - minBaseIdx = pool->getBaseIdx(minBase); - maxBaseIdx = pool->getBaseIdx(maxBase); - wordSpacing = fixedPitch ? fixedPitch : maxWordSpacing * fontSize; - - // find the rest of the words in this line - while (1) { - - // find the left-most word whose baseline is in the range for - // this line - bestWordBaseIdx = 0; - bestWord0 = bestWord1 = NULL; - overlap = gFalse; - for (baseIdx = minBaseIdx; - !overlap && baseIdx <= maxBaseIdx; - ++baseIdx) { - for (word0 = NULL, word1 = pool->getPool(baseIdx); - word1; - word0 = word1, word1 = word1->next) { - if (word1->base >= minBase && - word1->base <= maxBase) { - delta = lastWord->primaryDelta(word1); - if (delta < minCharSpacing * fontSize) { - overlap = gTrue; - break; - } else { - if (delta < wordSpacing && - (!bestWord1 || word1->primaryCmp(bestWord1) < 0)) { - bestWordBaseIdx = baseIdx; - bestWord0 = word0; - bestWord1 = word1; - } - break; - } - } - } - } - if (overlap || !bestWord1) { - break; - } - - // remove it from the pool, and add it to the line - if (bestWord0) { - bestWord0->next = bestWord1->next; - } else { - pool->setPool(bestWordBaseIdx, bestWord1->next); - } - bestWord1->next = NULL; - line->addWord(bestWord1); - lastWord = bestWord1; - } - - // add the line - if (curLine && line->cmpYX(curLine) > 0) { - line0 = curLine; - line1 = curLine->next; - } else { - line0 = NULL; - line1 = lines; - } - for (; - line1 && line->cmpYX(line1) > 0; - line0 = line1, line1 = line1->next) ; - if (line0) { - line0->next = line; - } else { - lines = line; - } - line->next = line1; - curLine = line; - line->coalesce(uMap); - charCount += line->len; - ++nLines; - } - - // sort lines into xy order for column assignment - lineArray = (TextLine **)gmallocn(nLines, sizeof(TextLine *)); - for (line = lines, i = 0; line; line = line->next, ++i) { - lineArray[i] = line; - } - qsort(lineArray, nLines, sizeof(TextLine *), &TextLine::cmpXY); - - // column assignment - nColumns = 0; - if (fixedPitch) { - for (i = 0; i < nLines; ++i) { - line0 = lineArray[i]; - col1 = 0; // make gcc happy - switch (rot) { - case 0: - col1 = (int)((line0->xMin - xMin) / fixedPitch + 0.5); - break; - case 1: - col1 = (int)((line0->yMin - yMin) / fixedPitch + 0.5); - break; - case 2: - col1 = (int)((xMax - line0->xMax) / fixedPitch + 0.5); - break; - case 3: - col1 = (int)((yMax - line0->yMax) / fixedPitch + 0.5); - break; - } - for (k = 0; k <= line0->len; ++k) { - line0->col[k] += col1; - } - if (line0->col[line0->len] > nColumns) { - nColumns = line0->col[line0->len]; - } - } - } else { - for (i = 0; i < nLines; ++i) { - line0 = lineArray[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - line1 = lineArray[j]; - if (line1->primaryDelta(line0) >= 0) { - col2 = line1->col[line1->len] + 1; - } else { - k = 0; // make gcc happy - switch (rot) { - case 0: - for (k = 0; - k < line1->len && - line0->xMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 1: - for (k = 0; - k < line1->len && - line0->yMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 2: - for (k = 0; - k < line1->len && - line0->xMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 3: - for (k = 0; - k < line1->len && - line0->yMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - } - col2 = line1->col[k]; - } - if (col2 > col1) { - col1 = col2; - } - } - for (k = 0; k <= line0->len; ++k) { - line0->col[k] += col1; - } - if (line0->col[line0->len] > nColumns) { - nColumns = line0->col[line0->len]; - } - } - } - gfree(lineArray); -} - -void TextBlock::updatePriMinMax(TextBlock *blk) { - double newPriMin, newPriMax; - GBool gotPriMin, gotPriMax; - - gotPriMin = gotPriMax = gFalse; - newPriMin = newPriMax = 0; // make gcc happy - switch (page->primaryRot) { - case 0: - case 2: - if (blk->yMin < yMax && blk->yMax > yMin) { - if (blk->xMin < xMin) { - newPriMin = blk->xMax; - gotPriMin = gTrue; - } - if (blk->xMax > xMax) { - newPriMax = blk->xMin; - gotPriMax = gTrue; - } - } - break; - case 1: - case 3: - if (blk->xMin < xMax && blk->xMax > xMin) { - if (blk->yMin < yMin) { - newPriMin = blk->yMax; - gotPriMin = gTrue; - } - if (blk->yMax > yMax) { - newPriMax = blk->yMin; - gotPriMax = gTrue; - } - } - break; - } - if (gotPriMin) { - if (newPriMin > xMin) { - newPriMin = xMin; - } - if (newPriMin > priMin) { - priMin = newPriMin; - } - } - if (gotPriMax) { - if (newPriMax < xMax) { - newPriMax = xMax; - } - if (newPriMax < priMax) { - priMax = newPriMax; - } - } -} - -int TextBlock::cmpXYPrimaryRot(const void *p1, const void *p2) { - TextBlock *blk1 = *(TextBlock **)p1; - TextBlock *blk2 = *(TextBlock **)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (blk1->page->primaryRot) { - case 0: - if ((cmp = blk1->xMin - blk2->xMin) == 0) { - cmp = blk1->yMin - blk2->yMin; + lines = linesA; + xMin = yMin = xMax = yMax = 0; + for (i = 0; i < lines->getLength(); ++i) { + line = (TextLine *)lines->get(i); + if (i == 0 || line->xMin < xMin) { + xMin = line->xMin; } - break; - case 1: - if ((cmp = blk1->yMin - blk2->yMin) == 0) { - cmp = blk2->xMax - blk1->xMax; + if (i == 0 || line->yMin < yMin) { + yMin = line->yMin; } - break; - case 2: - if ((cmp = blk2->xMax - blk1->xMax) == 0) { - cmp = blk2->yMin - blk1->yMin; + if (i == 0 || line->xMax > xMax) { + xMax = line->xMax; } - break; - case 3: - if ((cmp = blk2->yMax - blk1->yMax) == 0) { - cmp = blk1->xMax - blk2->xMax; + if (i == 0 || line->yMax > yMax) { + yMax = line->yMax; } - break; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; } -int TextBlock::cmpYXPrimaryRot(const void *p1, const void *p2) { - TextBlock *blk1 = *(TextBlock **)p1; - TextBlock *blk2 = *(TextBlock **)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (blk1->page->primaryRot) { - case 0: - if ((cmp = blk1->yMin - blk2->yMin) == 0) { - cmp = blk1->xMin - blk2->xMin; - } - break; - case 1: - if ((cmp = blk2->xMax - blk1->xMax) == 0) { - cmp = blk1->yMin - blk2->yMin; - } - break; - case 2: - if ((cmp = blk2->yMin - blk1->yMin) == 0) { - cmp = blk2->xMax - blk1->xMax; - } - break; - case 3: - if ((cmp = blk1->xMax - blk2->xMax) == 0) { - cmp = blk2->yMax - blk1->yMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; +TextParagraph::~TextParagraph() { + deleteGList(lines, TextLine); } -int TextBlock::primaryCmp(TextBlock *blk) { - double cmp; +//------------------------------------------------------------------------ +// TextColumn +//------------------------------------------------------------------------ - cmp = 0; // make gcc happy - switch (rot) { - case 0: - cmp = xMin - blk->xMin; - break; - case 1: - cmp = yMin - blk->yMin; - break; - case 2: - cmp = blk->xMax - xMax; - break; - case 3: - cmp = blk->yMax - yMax; - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; +TextColumn::TextColumn(GList *paragraphsA, double xMinA, double yMinA, + double xMaxA, double yMaxA) { + paragraphs = paragraphsA; + xMin = xMinA; + yMin = yMinA; + xMax = xMaxA; + yMax = yMaxA; + px = py = 0; + pw = ph = 0; } -double TextBlock::secondaryDelta(TextBlock *blk) { - double delta; - - delta = 0; // make gcc happy - switch (rot) { - case 0: - delta = blk->yMin - yMax; - break; - case 1: - delta = xMin - blk->xMax; - break; - case 2: - delta = yMin - blk->yMax; - break; - case 3: - delta = blk->xMin - xMax; - break; - } - return delta; +TextColumn::~TextColumn() { + deleteGList(paragraphs, TextParagraph); } -GBool TextBlock::isBelow(TextBlock *blk) { - GBool below; +int TextColumn::cmpX(const void *p1, const void *p2) { + const TextColumn *col1 = *(const TextColumn **)p1; + const TextColumn *col2 = *(const TextColumn **)p2; - below = gFalse; // make gcc happy - switch (page->primaryRot) { - case 0: - below = xMin >= blk->priMin && xMax <= blk->priMax && - yMin > blk->yMin; - break; - case 1: - below = yMin >= blk->priMin && yMax <= blk->priMax && - xMax < blk->xMax; - break; - case 2: - below = xMin >= blk->priMin && xMax <= blk->priMax && - yMax < blk->yMax; - break; - case 3: - below = yMin >= blk->priMin && yMax <= blk->priMax && - xMin > blk->xMin; - break; + if (col1->xMin < col2->xMin) { + return -1; + } else if (col1->xMin > col2->xMin) { + return 1; + } else { + return 0; } - - return below; } -//------------------------------------------------------------------------ -// TextFlow -//------------------------------------------------------------------------ - -TextFlow::TextFlow(TextPage *pageA, TextBlock *blk) { - page = pageA; - xMin = blk->xMin; - xMax = blk->xMax; - yMin = blk->yMin; - yMax = blk->yMax; - priMin = blk->priMin; - priMax = blk->priMax; - blocks = lastBlk = blk; - next = NULL; -} +int TextColumn::cmpY(const void *p1, const void *p2) { + const TextColumn *col1 = *(const TextColumn **)p1; + const TextColumn *col2 = *(const TextColumn **)p2; -TextFlow::~TextFlow() { - TextBlock *blk; - - while (blocks) { - blk = blocks; - blocks = blocks->next; - delete blk; - } -} - -void TextFlow::addBlock(TextBlock *blk) { - if (lastBlk) { - lastBlk->next = blk; + if (col1->yMin < col2->yMin) { + return -1; + } else if (col1->yMin > col2->yMin) { + return 1; } else { - blocks = blk; - } - lastBlk = blk; - if (blk->xMin < xMin) { - xMin = blk->xMin; - } - if (blk->xMax > xMax) { - xMax = blk->xMax; - } - if (blk->yMin < yMin) { - yMin = blk->yMin; - } - if (blk->yMax > yMax) { - yMax = blk->yMax; + return 0; } } -GBool TextFlow::blockFits(TextBlock *blk, TextBlock *prevBlk) { - GBool fits; +int TextColumn::cmpPX(const void *p1, const void *p2) { + const TextColumn *col1 = *(const TextColumn **)p1; + const TextColumn *col2 = *(const TextColumn **)p2; - // lower blocks must use smaller fonts - if (blk->lines->words->fontSize > lastBlk->lines->words->fontSize) { - return gFalse; - } - - fits = gFalse; // make gcc happy - switch (page->primaryRot) { - case 0: - fits = blk->xMin >= priMin && blk->xMax <= priMax; - break; - case 1: - fits = blk->yMin >= priMin && blk->yMax <= priMax; - break; - case 2: - fits = blk->xMin >= priMin && blk->xMax <= priMax; - break; - case 3: - fits = blk->yMin >= priMin && blk->yMax <= priMax; - break; + if (col1->px < col2->px) { + return -1; + } else if (col1->px > col2->px) { + return 1; + } else { + return 0; } - return fits; } -#if TEXTOUT_WORD_LIST - //------------------------------------------------------------------------ // TextWordList //------------------------------------------------------------------------ -TextWordList::TextWordList(TextPage *text, GBool physLayout) { - TextFlow *flow; - TextBlock *blk; - TextLine *line; - TextWord *word; - TextWord **wordArray; - int nWords, i; - - words = new GList(); - - if (text->rawOrder) { - for (word = text->rawWords; word; word = word->next) { - words->append(word); - } - - } else if (physLayout) { - // this is inefficient, but it's also the least useful of these - // three cases - nWords = 0; - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - ++nWords; - } - } - } - } - wordArray = (TextWord **)gmallocn(nWords, sizeof(TextWord *)); - i = 0; - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - wordArray[i++] = word; - } - } - } - } - qsort(wordArray, nWords, sizeof(TextWord *), &TextWord::cmpYX); - for (i = 0; i < nWords; ++i) { - words->append(wordArray[i]); - } - gfree(wordArray); - - } else { - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - words->append(word); - } - } - } - } - } +TextWordList::TextWordList(GList *wordsA) { + words = wordsA; } TextWordList::~TextWordList() { - delete words; + deleteGList(words, TextWord); } int TextWordList::getLength() { @@ -1831,54 +912,47 @@ TextWord *TextWordList::get(int idx) { return (TextWord *)words->get(idx); } -#endif // TEXTOUT_WORD_LIST - //------------------------------------------------------------------------ // TextPage //------------------------------------------------------------------------ -TextPage::TextPage(GBool rawOrderA) { - int rot; - - rawOrder = rawOrderA; - curWord = NULL; +TextPage::TextPage(TextOutputControl *controlA) { + control = *controlA; + pageWidth = pageHeight = 0; charPos = 0; curFont = NULL; curFontSize = 0; - nest = 0; + curRot = 0; nTinyChars = 0; - lastCharOverlap = gFalse; actualText = NULL; actualTextLen = 0; + actualTextX0 = 0; + actualTextY0 = 0; + actualTextX1 = 0; + actualTextY1 = 0; actualTextNBytes = 0; - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - pools[rot] = new TextPool(); - } - } - flows = NULL; - blocks = NULL; - rawWords = NULL; - rawLastWord = NULL; + + chars = new GList(); fonts = new GList(); - lastFindXMin = lastFindYMin = 0; - haveLastFind = gFalse; + underlines = new GList(); links = new GList(); + + findCols = NULL; + findLR = gTrue; + lastFindXMin = lastFindYMin = 0; + haveLastFind = gFalse; } TextPage::~TextPage() { - int rot; - clear(); - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - delete pools[rot]; - } - } - delete fonts; + deleteGList(chars, TextChar); + deleteGList(fonts, TextFontInfo); deleteGList(underlines, TextUnderline); deleteGList(links, TextLink); + if (findCols) { + deleteGList(findCols, TextColumn); + } } void TextPage::startPage(GfxState *state) { @@ -1891,64 +965,33 @@ void TextPage::startPage(GfxState *state) { } } -void TextPage::endPage() { - if (curWord) { - endWord(); - } -} - void TextPage::clear() { - int rot; - TextFlow *flow; - TextWord *word; - - if (curWord) { - delete curWord; - curWord = NULL; - } - gfree(actualText); - if (rawOrder) { - while (rawWords) { - word = rawWords; - rawWords = rawWords->next; - delete word; - } - } else { - for (rot = 0; rot < 4; ++rot) { - delete pools[rot]; - } - while (flows) { - flow = flows; - flows = flows->next; - delete flow; - } - gfree(blocks); - } - deleteGList(fonts, TextFontInfo); - deleteGList(underlines, TextUnderline); - deleteGList(links, TextLink); - - curWord = NULL; + pageWidth = pageHeight = 0; charPos = 0; curFont = NULL; curFontSize = 0; - nest = 0; + curRot = 0; nTinyChars = 0; + gfree(actualText); actualText = NULL; actualTextLen = 0; actualTextNBytes = 0; - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - pools[rot] = new TextPool(); - } - } - flows = NULL; - blocks = NULL; - rawWords = NULL; - rawLastWord = NULL; + deleteGList(chars, TextChar); + chars = new GList(); + deleteGList(fonts, TextFontInfo); fonts = new GList(); + deleteGList(underlines, TextUnderline); underlines = new GList(); + deleteGList(links, TextLink); links = new GList(); + + if (findCols) { + deleteGList(findCols, TextColumn); + findCols = NULL; + } + findLR = gTrue; + lastFindXMin = lastFindYMin = 0; + haveLastFind = gFalse; } void TextPage::updateFont(GfxState *state) { @@ -1957,6 +1000,7 @@ void TextPage::updateFont(GfxState *state) { char *name; int code, mCode, letterCode, anyCode; double w; + double m[4], m2[4]; int i; // get the font info object @@ -2017,55 +1061,36 @@ void TextPage::updateFont(GfxState *state) { curFontSize *= fabs(fm[3] / fm[0]); } } -} - -void TextPage::beginWord(GfxState *state, double x0, double y0) { - double *fontm; - double m[4], m2[4]; - int rot; - - // This check is needed because Type 3 characters can contain - // text-drawing operations (when TextPage is being used via - // {X,Win}SplashOutputDev rather than TextOutputDev). - if (curWord) { - ++nest; - return; - } // compute the rotation state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); - if (state->getFont()->getType() == fontType3) { - fontm = state->getFont()->getFontMatrix(); - m2[0] = fontm[0] * m[0] + fontm[1] * m[2]; - m2[1] = fontm[0] * m[1] + fontm[1] * m[3]; - m2[2] = fontm[2] * m[0] + fontm[3] * m[2]; - m2[3] = fontm[2] * m[1] + fontm[3] * m[3]; + if (gfxFont && gfxFont->getType() == fontType3) { + fm = gfxFont->getFontMatrix(); + m2[0] = fm[0] * m[0] + fm[1] * m[2]; + m2[1] = fm[0] * m[1] + fm[1] * m[3]; + m2[2] = fm[2] * m[0] + fm[3] * m[2]; + m2[3] = fm[2] * m[1] + fm[3] * m[3]; m[0] = m2[0]; m[1] = m2[1]; m[2] = m2[2]; m[3] = m2[3]; } if (fabs(m[0] * m[3]) > fabs(m[1] * m[2])) { - rot = (m[0] > 0 || m[3] < 0) ? 0 : 2; + curRot = (m[0] > 0 || m[3] < 0) ? 0 : 2; } else { - rot = (m[2] > 0) ? 1 : 3; + curRot = (m[2] > 0) ? 1 : 3; } - - // for vertical writing mode, the lines are effectively rotated 90 - // degrees - if (state->getFont()->getWMode()) { - rot = (rot + 1) & 3; - } - - curWord = new TextWord(state, rot, x0, y0, curFont, curFontSize); } void TextPage::addChar(GfxState *state, double x, double y, double dx, double dy, CharCode c, int nBytes, Unicode *u, int uLen) { - double x1, y1, w1, h1, dx2, dy2, base, sp, delta; - GBool overlap; - int i; + double x1, y1, x2, y2, w1, h1, dx2, dy2, ascent, descent, sp; + double xMin, yMin, xMax, yMax; + double clipXMin, clipYMin, clipXMax, clipYMax; + GfxRGB rgb; + GBool clipped, rtl; + int i, j; // if we're in an ActualText span, save the position info (the // ActualText chars will be added by TextPage::endActualText()). @@ -2109,90 +1134,93 @@ void TextPage::addChar(GfxState *state, double x, double y, } } - // break words at space character + // skip space characters if (uLen == 1 && u[0] == (Unicode)0x20) { charPos += nBytes; - endWord(); return; } - // start a new word if: - // (1) this character doesn't fall in the right place relative to - // the end of the previous word (this places upper and lower - // constraints on the position deltas along both the primary - // and secondary axes), or - // (2) this character overlaps the previous one (duplicated text), or - // (3) the previous character was an overlap (we want each duplicated - // character to be in a word by itself at this stage), - // (4) the font or font size has changed - if (curWord && curWord->len > 0) { - base = sp = delta = 0; // make gcc happy - switch (curWord->rot) { - case 0: - base = y1; - sp = x1 - curWord->xMax; - delta = x1 - curWord->edge[curWord->len - 1]; - break; - case 1: - base = x1; - sp = y1 - curWord->yMax; - delta = y1 - curWord->edge[curWord->len - 1]; - break; - case 2: - base = y1; - sp = curWord->xMin - x1; - delta = curWord->edge[curWord->len - 1] - x1; - break; - case 3: - base = x1; - sp = curWord->yMin - y1; - delta = curWord->edge[curWord->len - 1] - y1; - break; + // check for clipping + clipped = gFalse; + if (control.clipText) { + state->getClipBBox(&clipXMin, &clipYMin, &clipXMax, &clipYMax); + if (x1 + 0.1 * w1 < clipXMin || x1 + 0.9 * w1 > clipXMax || + y1 + 0.1 * h1 < clipYMin || y1 + 0.9 * h1 > clipYMax) { + clipped = gTrue; } - overlap = fabs(delta) < dupMaxPriDelta * curWord->fontSize && - fabs(base - curWord->base) < dupMaxSecDelta * curWord->fontSize; - if (overlap || lastCharOverlap || - sp < -minDupBreakOverlap * curWord->fontSize || - sp > minWordBreakSpace * curWord->fontSize || - fabs(base - curWord->base) > 0.5 || - curFont != curWord->font || - curFontSize != curWord->fontSize) { - endWord(); - } - lastCharOverlap = overlap; - } else { - lastCharOverlap = gFalse; } - if (uLen != 0) { - // start a new word if needed - if (!curWord) { - beginWord(state, x, y); - } + // add the characters + if (uLen > 0) { - // page rotation and/or transform matrices can cause text to be - // drawn in reverse order -- in this case, swap the begin/end - // coordinates and break text into individual chars - if ((curWord->rot == 0 && w1 < 0) || - (curWord->rot == 1 && h1 < 0) || - (curWord->rot == 2 && w1 > 0) || - (curWord->rot == 3 && h1 > 0)) { - endWord(); - beginWord(state, x + dx, y + dy); - x1 += w1; - y1 += h1; - w1 = -w1; - h1 = -h1; + // handle right-to-left ligatures: if there are multiple Unicode + // characters, and they're all right-to-left, insert them in + // right-to-left order + if (uLen > 1) { + rtl = gTrue; + for (i = 0; i < uLen; ++i) { + if (!unicodeTypeR(u[i])) { + rtl = gFalse; + break; + } + } + } else { + rtl = gFalse; } - // add the characters to the current word w1 /= uLen; h1 /= uLen; + ascent = curFont->ascent * curFontSize; + descent = curFont->descent * curFontSize; for (i = 0; i < uLen; ++i) { - curWord->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, - charPos, nBytes, u[i]); + x2 = x1 + i * w1; + y2 = y1 + i * h1; + switch (curRot) { + case 0: + default: + xMin = x2; + xMax = x2 + w1; + yMin = y2 - ascent; + yMax = y2 - descent; + break; + case 1: + xMin = x2 + descent; + xMax = x2 + ascent; + yMin = y2; + yMax = y2 + h1; + break; + case 2: + xMin = x2 + w1; + xMax = x2; + yMin = y2 + descent; + yMax = y2 + ascent; + break; + case 3: + xMin = x2 - ascent; + xMax = x2 - descent; + yMin = y2 + h1; + yMax = y2; + break; + } + if ((state->getRender() & 3) == 1) { + state->getStrokeRGB(&rgb); + } else { + state->getFillRGB(&rgb); + } + if (rtl) { + j = uLen - 1 - i; + } else { + j = i; + } + chars->append(new TextChar(u[j], charPos, nBytes, xMin, yMin, xMax, yMax, + curRot, clipped, + state->getRender() == 3, + curFont, curFontSize, + colToDbl(rgb.r), colToDbl(rgb.g), + colToDbl(rgb.b))); } } + charPos += nBytes; } @@ -2229,901 +1257,2381 @@ void TextPage::endActualText(GfxState *state) { actualTextNBytes = gFalse; } -void TextPage::endWord() { - // This check is needed because Type 3 characters can contain - // text-drawing operations (when TextPage is being used via - // {X,Win}SplashOutputDev rather than TextOutputDev). - if (nest > 0) { - --nest; +void TextPage::addUnderline(double x0, double y0, double x1, double y1) { + underlines->append(new TextUnderline(x0, y0, x1, y1)); +} + +void TextPage::addLink(double xMin, double yMin, double xMax, double yMax, + Link *link) { + GString *uri; + + if (link && link->getAction() && link->getAction()->getKind() == actionURI) { + uri = ((LinkURI *)link->getAction())->getURI()->copy(); + links->append(new TextLink(xMin, yMin, xMax, yMax, uri)); + } +} + +//------------------------------------------------------------------------ +// TextPage: output +//------------------------------------------------------------------------ + +void TextPage::write(void *outputStream, TextOutputFunc outputFunc) { + UnicodeMap *uMap; + char space[8], eol[16], eop[8]; + int spaceLen, eolLen, eopLen; + GBool pageBreaks; + + // get the output encoding + if (!(uMap = globalParams->getTextEncoding())) { return; } + spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); + eolLen = 0; // make gcc happy + switch (globalParams->getTextEOL()) { + case eolUnix: + eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); + break; + case eolDOS: + eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); + eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen); + break; + case eolMac: + eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); + break; + } + eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop)); + pageBreaks = globalParams->getTextPageBreaks(); - if (curWord) { - addWord(curWord); - curWord = NULL; + switch (control.mode) { + case textOutReadingOrder: + writeReadingOrder(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + case textOutPhysLayout: + case textOutTableLayout: + writePhysLayout(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + case textOutLinePrinter: + writeLinePrinter(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + case textOutRawOrder: + writeRaw(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + } + + // end of page + if (pageBreaks) { + (*outputFunc)(outputStream, eop, eopLen); } + + uMap->decRefCnt(); } -void TextPage::addWord(TextWord *word) { - // throw away zero-length words -- they don't have valid xMin/xMax - // values, and they're useless anyway - if (word->len == 0) { - delete word; +void TextPage::writeReadingOrder(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextBlock *tree; + TextColumn *col; + TextParagraph *par; + TextLine *line; + GList *columns; + GBool primaryLR; + GString *s; + int colIdx, parIdx, lineIdx, rot, n; + + rot = rotateChars(chars); + primaryLR = checkPrimaryLR(chars); + tree = splitChars(chars); +#if 0 //~debug + dumpTree(tree); +#endif + if (!tree) { + // no text + unrotateChars(chars, rot); return; } + columns = buildColumns(tree); + delete tree; + unrotateChars(chars, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); + } +#if 0 //~debug + dumpColumns(columns); +#endif - if (rawOrder) { - if (rawLastWord) { - rawLastWord->next = word; - } else { - rawWords = word; + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + n = line->len; + if (line->hyphenated && lineIdx + 1 < par->lines->getLength()) { + --n; + } + s = new GString(); + encodeFragment(line->text, n, uMap, primaryLR, s); + if (lineIdx + 1 < par->lines->getLength() && !line->hyphenated) { + s->append(space, spaceLen); + } + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + delete s; + } + (*outputFunc)(outputStream, eol, eolLen); } - rawLastWord = word; - } else { - pools[word->rot]->addWord(word); + (*outputFunc)(outputStream, eol, eolLen); } -} -void TextPage::addUnderline(double x0, double y0, double x1, double y1) { - underlines->append(new TextUnderline(x0, y0, x1, y1)); + deleteGList(columns, TextColumn); } -void TextPage::addLink(int xMin, int yMin, int xMax, int yMax, Link *link) { - links->append(new TextLink(xMin, yMin, xMax, yMax, link)); +GList *TextPage::makeColumns() { + TextBlock *tree; + GList *columns; + + tree = splitChars(chars); + if (!tree) { + // no text + return new GList(); + } + columns = buildColumns(tree); + delete tree; + if (control.html) { + generateUnderlinesAndLinks(columns); + } + return columns; } -void TextPage::coalesce(GBool physLayout, double fixedPitch, GBool doHTML) { - UnicodeMap *uMap; - TextPool *pool; - TextWord *word0, *word1, *word2; +// This handles both physical layout and table layout modes. +void TextPage::writePhysLayout(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextBlock *tree; + GString **out; + int *outLen; + TextColumn *col; + TextParagraph *par; TextLine *line; - TextBlock *blkList, *blkStack, *blk, *lastBlk, *blk0, *blk1; - TextBlock **blkArray; - TextFlow *flow, *lastFlow; - TextUnderline *underline; - TextLink *link; - int rot, poolMinBaseIdx, baseIdx, startBaseIdx, endBaseIdx; - double minBase, maxBase, newMinBase, newMaxBase; - double fontSize, colSpace1, colSpace2, lineSpace, intraLineSpace, blkSpace; - GBool found; - int count[4]; - int lrCount; - int firstBlkIdx, nBlocksLeft; - int col1, col2; - int i, j, n; - - if (rawOrder) { - primaryRot = 0; - primaryLR = gTrue; + GList *columns; + GBool primaryLR; + int ph, colIdx, parIdx, lineIdx, rot, y, i; + +#if 0 //~debug + dumpChars(chars); +#endif + rot = rotateChars(chars); + primaryLR = checkPrimaryLR(chars); + tree = splitChars(chars); +#if 0 //~debug + dumpTree(tree); +#endif + if (!tree) { + // no text + unrotateChars(chars, rot); return; } + columns = buildColumns(tree); + delete tree; + unrotateChars(chars, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); + } + ph = assignPhysLayoutPositions(columns); +#if 0 //~debug + dumpColumns(columns); +#endif - uMap = globalParams->getTextEncoding(); - blkList = NULL; - lastBlk = NULL; - nBlocks = 0; - primaryRot = 0; - -#if 0 // for debugging - printf("*** initial words ***\n"); - for (rot = 0; rot < 4; ++rot) { - pool = pools[rot]; - for (baseIdx = pool->minBaseIdx; baseIdx <= pool->maxBaseIdx; ++baseIdx) { - for (word0 = pool->getPool(baseIdx); word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f rot=%d link=%p '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, rot*90, word0->link); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); + out = (GString **)gmallocn(ph, sizeof(GString *)); + outLen = (int *)gmallocn(ph, sizeof(int)); + for (i = 0; i < ph; ++i) { + out[i] = NULL; + outLen[i] = 0; + } + + columns->sort(&TextColumn::cmpPX); + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + y = col->py; + for (parIdx = 0; + parIdx < col->paragraphs->getLength() && y < ph; + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; + lineIdx < par->lines->getLength() && y < ph; + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + if (!out[y]) { + out[y] = new GString(); } - printf("'\n"); + while (outLen[y] < col->px + line->px) { + out[y]->append(space, spaceLen); + ++outLen[y]; + } + encodeFragment(line->text, line->len, uMap, primaryLR, out[y]); + outLen[y] += line->pw; + ++y; + } + if (parIdx + 1 < col->paragraphs->getLength()) { + ++y; } } } - printf("\n"); -#endif -#if 0 //~ for debugging - for (i = 0; i < underlines->getLength(); ++i) { - underline = (TextUnderline *)underlines->get(i); - printf("underline: x=%g..%g y=%g..%g horiz=%d\n", - underline->x0, underline->x1, underline->y0, underline->y1, - underline->horiz); + for (i = 0; i < ph; ++i) { + if (out[i]) { + (*outputFunc)(outputStream, out[i]->getCString(), out[i]->getLength()); + delete out[i]; + } + (*outputFunc)(outputStream, eol, eolLen); } -#endif - if (doHTML) { + gfree(out); + gfree(outLen); - //----- handle underlining - for (i = 0; i < underlines->getLength(); ++i) { - underline = (TextUnderline *)underlines->get(i); - if (underline->horiz) { - // rot = 0 - if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) { - startBaseIdx = pools[0]->getBaseIdx(underline->y0 + minUnderlineGap); - endBaseIdx = pools[0]->getBaseIdx(underline->y0 + maxUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) { - //~ need to check the y value against the word baseline - if (underline->x0 < word0->xMin + underlineSlack && - word0->xMax - underlineSlack < underline->x1) { - word0->underlined = gTrue; - } - } + deleteGList(columns, TextColumn); +} + +void TextPage::writeLinePrinter(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextChar *ch, *ch2; + GList *line; + GString *s; + char buf[8]; + double pitch, lineSpacing, delta; + double yMin0, yShift, xMin0, xShift; + double y, x; + int rot, n, i, j, k; + + rot = rotateChars(chars); + chars->sort(&TextChar::cmpX); + removeDuplicates(chars, 0); + chars->sort(&TextChar::cmpY); + + // get character pitch + if (control.fixedPitch > 0) { + pitch = control.fixedPitch; + } else { + // compute (approximate) character pitch + pitch = pageWidth; + for (i = 0; i < chars->getLength(); ++i) { + ch = (TextChar *)chars->get(i); + for (j = i + 1; j < chars->getLength(); ++j) { + ch2 = (TextChar *)chars->get(j); + if (ch2->yMin + ascentAdjustFactor * (ch2->yMax - ch2->yMin) < + ch->yMax - descentAdjustFactor * (ch->yMax - ch->yMin) && + ch->yMin + ascentAdjustFactor * (ch->yMax - ch->yMin) < + ch2->yMax - descentAdjustFactor * (ch2->yMax - ch2->yMin)) { + delta = fabs(ch2->xMin - ch->xMin); + if (delta > 0 && delta < pitch) { + pitch = delta; } } + } + } + } - // rot = 2 - if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) { - startBaseIdx = pools[2]->getBaseIdx(underline->y0 - maxUnderlineGap); - endBaseIdx = pools[2]->getBaseIdx(underline->y0 - minUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) { - if (underline->x0 < word0->xMin + underlineSlack && - word0->xMax - underlineSlack < underline->x1) { - word0->underlined = gTrue; - } - } - } + // get line spacing + if (control.fixedLineSpacing > 0) { + lineSpacing = control.fixedLineSpacing; + } else { + // compute (approximate) line spacing + lineSpacing = pageHeight; + i = 0; + while (i < chars->getLength()) { + ch = (TextChar *)chars->get(i); + // look for the first char that does not (substantially) + // vertically overlap this one + delta = 0; + for (++i; delta == 0 && i < chars->getLength(); ++i) { + ch2 = (TextChar *)chars->get(i); + if (ch2->yMin + ascentAdjustFactor * (ch2->yMax - ch2->yMin) > + ch->yMax - descentAdjustFactor * (ch->yMax - ch->yMin)) { + delta = ch2->yMin - ch->yMin; } + } + if (delta > 0 && delta < lineSpacing) { + lineSpacing = delta; + } + } + } + + // shift the grid to avoid problems with floating point accuracy -- + // for fixed line spacing, this avoids problems with + // dropping/inserting blank lines + if (chars->getLength()) { + yMin0 = ((TextChar *)chars->get(0))->yMin; + yShift = yMin0 - (int)(yMin0 / lineSpacing + 0.5) * lineSpacing + - 0.5 * lineSpacing; + } else { + yShift = 0; + } + + // for each line... + i = 0; + j = chars->getLength() - 1; + for (y = yShift; y < pageHeight; y += lineSpacing) { + + // get the characters in this line + line = new GList; + while (i < chars->getLength() && + ((TextChar *)chars->get(i))->yMin < y + lineSpacing) { + line->append(chars->get(i++)); + } + line->sort(&TextChar::cmpX); + + // shift the grid to avoid problems with floating point accuracy + // -- for fixed char spacing, this avoids problems with + // dropping/inserting spaces + if (line->getLength()) { + xMin0 = ((TextChar *)line->get(0))->xMin; + xShift = xMin0 - (int)(xMin0 / pitch + 0.5) * pitch - 0.5 * pitch; + } else { + xShift = 0; + } + + // write the line + s = new GString(); + x = xShift; + k = 0; + while (k < line->getLength()) { + ch = (TextChar *)line->get(k); + if (ch->xMin < x + pitch) { + n = uMap->mapUnicode(ch->c, buf, sizeof(buf)); + s->append(buf, n); + ++k; } else { - // rot = 1 - if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) { - startBaseIdx = pools[1]->getBaseIdx(underline->x0 - maxUnderlineGap); - endBaseIdx = pools[1]->getBaseIdx(underline->x0 - minUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) { - if (underline->y0 < word0->yMin + underlineSlack && - word0->yMax - underlineSlack < underline->y1) { - word0->underlined = gTrue; - } - } + s->append(space, spaceLen); + n = spaceLen; + } + x += (uMap->isUnicode() ? 1 : n) * pitch; + } + s->append(eol, eolLen); + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + delete s; + delete line; + } + + unrotateChars(chars, rot); +} + +void TextPage::writeRaw(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextChar *ch, *ch2; + GString *s; + char buf[8]; + int n, i; + + s = new GString(); + + for (i = 0; i < chars->getLength(); ++i) { + + // process one char + ch = (TextChar *)chars->get(i); + n = uMap->mapUnicode(ch->c, buf, sizeof(buf)); + s->append(buf, n); + + // check for space or eol + if (i+1 < chars->getLength()) { + ch2 = (TextChar *)chars->get(i+1); + if (ch2->rot != ch->rot) { + s->append(eol, eolLen); + } else { + switch (ch->rot) { + case 0: + default: + if (fabs(ch2->yMin - ch->yMin) > rawModeLineDelta * ch->fontSize || + ch2->xMin - ch->xMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch2->xMin - ch->xMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); + } + break; + case 1: + if (fabs(ch->xMax - ch2->xMax) > rawModeLineDelta * ch->fontSize || + ch2->yMin - ch->yMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch2->yMin - ch->yMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); + } + break; + case 2: + if (fabs(ch->yMax - ch2->yMax) > rawModeLineDelta * ch->fontSize || + ch->xMin - ch2->xMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch->xMin - ch2->xMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); + } + break; + case 3: + if (fabs(ch2->xMin - ch->xMin) > rawModeLineDelta * ch->fontSize || + ch->yMin - ch2->yMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch->yMin - ch2->yMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); } + break; } + } + } else { + s->append(eol, eolLen); + } - // rot = 3 - if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) { - startBaseIdx = pools[3]->getBaseIdx(underline->x0 + minUnderlineGap); - endBaseIdx = pools[3]->getBaseIdx(underline->x0 + maxUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) { - if (underline->y0 < word0->yMin + underlineSlack && - word0->yMax - underlineSlack < underline->y1) { - word0->underlined = gTrue; - } - } + if (s->getLength() > 1000) { + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + s->clear(); + } + } + + if (s->getLength() > 0) { + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + } + delete s; +} + +void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, + GBool primaryLR, GString *s) { + char lre[8], rle[8], popdf[8], buf[8]; + int lreLen, rleLen, popdfLen, n; + int i, j, k; + + if (uMap->isUnicode()) { + + lreLen = uMap->mapUnicode(0x202a, lre, sizeof(lre)); + rleLen = uMap->mapUnicode(0x202b, rle, sizeof(rle)); + popdfLen = uMap->mapUnicode(0x202c, popdf, sizeof(popdf)); + + if (primaryLR) { + + i = 0; + while (i < len) { + // output a left-to-right section + for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ; + for (k = i; k < j; ++k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); + } + i = j; + // output a right-to-left section + for (j = i; + j < len && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); + ++j) ; + if (j > i) { + s->append(rle, rleLen); + for (k = j - 1; k >= i; --k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); + } + s->append(popdf, popdfLen); + i = j; + } + } + + } else { + + // Note: This code treats numeric characters (European and + // Arabic/Indic) as left-to-right, which isn't strictly correct + // (incurs extra LRE/POPDF pairs), but does produce correct + // visual formatting. + s->append(rle, rleLen); + i = len - 1; + while (i >= 0) { + // output a right-to-left section + for (j = i; + j >= 0 && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); + --j) ; + for (k = i; k > j; --k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); + } + i = j; + // output a left-to-right section + for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ; + if (j < i) { + s->append(lre, lreLen); + for (k = j + 1; k <= i; ++k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); } + s->append(popdf, popdfLen); + i = j; } } + s->append(popdf, popdfLen); + } + + } else { + for (i = 0; i < len; ++i) { + n = uMap->mapUnicode(text[i], buf, sizeof(buf)); + s->append(buf, n); + } + } +} + +//------------------------------------------------------------------------ +// TextPage: layout analysis +//------------------------------------------------------------------------ + +// Determine primary (most common) rotation value. Rotate all chars +// to that primary rotation. +int TextPage::rotateChars(GList *charsA) { + TextChar *ch; + int nChars[4]; + double xMin, yMin, xMax, yMax, t; + int rot, i; + + // determine primary rotation + nChars[0] = nChars[1] = nChars[2] = nChars[3] = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + ++nChars[ch->rot]; + } + rot = 0; + for (i = 1; i < 4; ++i) { + if (nChars[i] > nChars[rot]) { + rot = i; + } + } + + // rotate + switch (rot) { + case 0: + default: + break; + case 1: + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xMin = ch->yMin; + xMax = ch->yMax; + yMin = pageWidth - ch->xMax; + yMax = pageWidth - ch->xMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + ch->rot = (ch->rot + 3) & 3; + } + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + break; + case 2: + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xMin = pageWidth - ch->xMax; + xMax = pageWidth - ch->xMin; + yMin = pageHeight - ch->yMax; + yMax = pageHeight - ch->yMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + ch->rot = (ch->rot + 2) & 3; } + break; + case 3: + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xMin = pageHeight - ch->yMax; + xMax = pageHeight - ch->yMin; + yMin = ch->xMin; + yMax = ch->xMax; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + ch->rot = (ch->rot + 1) & 3; + } + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + break; + } + + return rot; +} + +// Rotate the TextUnderlines and TextLinks to match the transform +// performed by rotateChars(). +void TextPage::rotateUnderlinesAndLinks(int rot) { + TextUnderline *underline; + TextLink *link; + double xMin, yMin, xMax, yMax; + int i; - //----- handle links + switch (rot) { + case 0: + default: + break; + case 1: + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + xMin = underline->y0; + xMax = underline->y1; + yMin = pageWidth - underline->x1; + yMax = pageWidth - underline->x0; + underline->x0 = xMin; + underline->x1 = xMax; + underline->y0 = yMin; + underline->y1 = yMax; + underline->horiz = !underline->horiz; + } for (i = 0; i < links->getLength(); ++i) { link = (TextLink *)links->get(i); + xMin = link->yMin; + xMax = link->yMax; + yMin = pageWidth - link->xMax; + yMax = pageWidth - link->xMin; + link->xMin = xMin; + link->xMax = xMax; + link->yMin = yMin; + link->yMax = yMax; + } + break; + case 2: + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + xMin = pageWidth - underline->x1; + xMax = pageWidth - underline->x0; + yMin = pageHeight - underline->y1; + yMax = pageHeight - underline->y0; + underline->x0 = xMin; + underline->x1 = xMax; + underline->y0 = yMin; + underline->y1 = yMax; + } + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + xMin = pageWidth - link->xMax; + xMax = pageWidth - link->xMin; + yMin = pageHeight - link->yMax; + yMax = pageHeight - link->yMin; + link->xMin = xMin; + link->xMax = xMax; + link->yMin = yMin; + link->yMax = yMax; + } + break; + case 3: + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + xMin = pageHeight - underline->y1; + xMax = pageHeight - underline->y0; + yMin = underline->x0; + yMax = underline->x1; + underline->x0 = xMin; + underline->x1 = xMax; + underline->y0 = yMin; + underline->y1 = yMax; + underline->horiz = !underline->horiz; + } + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + xMin = pageHeight - link->yMax; + xMax = pageHeight - link->yMin; + yMin = link->xMin; + yMax = link->xMax; + link->xMin = xMin; + link->xMax = xMax; + link->yMin = yMin; + link->yMax = yMax; + } + break; + } +} - // rot = 0 - if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) { - startBaseIdx = pools[0]->getBaseIdx(link->yMin); - endBaseIdx = pools[0]->getBaseIdx(link->yMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) { - if (link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax && - link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax) { - word0->link = link->link; - } +// Undo the coordinate transform performed by rotateChars(). +void TextPage::unrotateChars(GList *charsA, int rot) { + TextChar *ch; + double xMin, yMin, xMax, yMax, t; + int i; + + switch (rot) { + case 0: + default: + // no transform + break; + case 1: + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xMin = pageWidth - ch->yMax; + xMax = pageWidth - ch->yMin; + yMin = ch->xMin; + yMax = ch->xMax; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + ch->rot = (ch->rot + 1) & 3; + } + break; + case 2: + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xMin = pageWidth - ch->xMax; + xMax = pageWidth - ch->xMin; + yMin = pageHeight - ch->yMax; + yMax = pageHeight - ch->yMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + ch->rot = (ch->rot + 2) & 3; + } + break; + case 3: + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xMin = ch->yMin; + xMax = ch->yMax; + yMin = pageHeight - ch->xMax; + yMax = pageHeight - ch->xMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + ch->rot = (ch->rot + 3) & 3; + } + break; + } +} + +// Undo the coordinate transform performed by rotateChars(). +void TextPage::unrotateColumns(GList *columns, int rot) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word; + double xMin, yMin, xMax, yMax, t; + int colIdx, parIdx, lineIdx, wordIdx, i; + + switch (rot) { + case 0: + default: + // no transform + break; + case 1: + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + xMin = pageWidth - col->yMax; + xMax = pageWidth - col->yMin; + yMin = col->xMin; + yMax = col->xMax; + col->xMin = xMin; + col->xMax = xMax; + col->yMin = yMin; + col->yMax = yMax; + for (parIdx = 0; + parIdx < col->paragraphs->getLength(); + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + xMin = pageWidth - par->yMax; + xMax = pageWidth - par->yMin; + yMin = par->xMin; + yMax = par->xMax; + par->xMin = xMin; + par->xMax = xMax; + par->yMin = yMin; + par->yMax = yMax; + for (lineIdx = 0; + lineIdx < par->lines->getLength(); + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + xMin = pageWidth - line->yMax; + xMax = pageWidth - line->yMin; + yMin = line->xMin; + yMax = line->xMax; + line->xMin = xMin; + line->xMax = xMax; + line->yMin = yMin; + line->yMax = yMax; + line->rot = (line->rot + 1) & 3; + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + xMin = pageWidth - word->yMax; + xMax = pageWidth - word->yMin; + yMin = word->xMin; + yMax = word->xMax; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + word->rot = (word->rot + 1) & 3; } } } - - // rot = 2 - if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) { - startBaseIdx = pools[2]->getBaseIdx(link->yMin); - endBaseIdx = pools[2]->getBaseIdx(link->yMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) { - if (link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax && - link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax) { - word0->link = link->link; + } + break; + case 2: + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + xMin = pageWidth - col->xMax; + xMax = pageWidth - col->xMin; + yMin = pageHeight - col->yMax; + yMax = pageHeight - col->yMin; + col->xMin = xMin; + col->xMax = xMax; + col->yMin = yMin; + col->yMax = yMax; + for (parIdx = 0; + parIdx < col->paragraphs->getLength(); + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + xMin = pageWidth - par->xMax; + xMax = pageWidth - par->xMin; + yMin = pageHeight - par->yMax; + yMax = pageHeight - par->yMin; + par->xMin = xMin; + par->xMax = xMax; + par->yMin = yMin; + par->yMax = yMax; + for (lineIdx = 0; + lineIdx < par->lines->getLength(); + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + xMin = pageWidth - line->xMax; + xMax = pageWidth - line->xMin; + yMin = pageHeight - line->yMax; + yMax = pageHeight - line->yMin; + line->xMin = xMin; + line->xMax = xMax; + line->yMin = yMin; + line->yMax = yMax; + line->rot = (line->rot + 2) & 3; + for (i = 0; i <= line->len; ++i) { + line->edge[i] = pageWidth - line->edge[i]; + } + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + xMin = pageWidth - word->xMax; + xMax = pageWidth - word->xMin; + yMin = pageHeight - word->yMax; + yMax = pageHeight - word->yMin; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + word->rot = (word->rot + 2) & 3; + for (i = 0; i <= word->len; ++i) { + word->edge[i] = pageWidth - word->edge[i]; } } } } - - // rot = 1 - if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) { - startBaseIdx = pools[1]->getBaseIdx(link->xMin); - endBaseIdx = pools[1]->getBaseIdx(link->xMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) { - if (link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax && - link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax) { - word0->link = link->link; + } + break; + case 3: + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + xMin = col->yMin; + xMax = col->yMax; + yMin = pageHeight - col->xMax; + yMax = pageHeight - col->xMin; + col->xMin = xMin; + col->xMax = xMax; + col->yMin = yMin; + col->yMax = yMax; + for (parIdx = 0; + parIdx < col->paragraphs->getLength(); + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + xMin = par->yMin; + xMax = par->yMax; + yMin = pageHeight - par->xMax; + yMax = pageHeight - par->xMin; + par->xMin = xMin; + par->xMax = xMax; + par->yMin = yMin; + par->yMax = yMax; + for (lineIdx = 0; + lineIdx < par->lines->getLength(); + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + xMin = line->yMin; + xMax = line->yMax; + yMin = pageHeight - line->xMax; + yMax = pageHeight - line->xMin; + line->xMin = xMin; + line->xMax = xMax; + line->yMin = yMin; + line->yMax = yMax; + line->rot = (line->rot + 3) & 3; + for (i = 0; i <= line->len; ++i) { + line->edge[i] = pageHeight - line->edge[i]; + } + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + xMin = word->yMin; + xMax = word->yMax; + yMin = pageHeight - word->xMax; + yMax = pageHeight - word->xMin; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + word->rot = (word->rot + 3) & 3; + for (i = 0; i <= word->len; ++i) { + word->edge[i] = pageHeight - word->edge[i]; } } } } + } + break; + } +} - // rot = 3 - if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) { - startBaseIdx = pools[3]->getBaseIdx(link->xMin); - endBaseIdx = pools[3]->getBaseIdx(link->xMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) { - if (link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax && - link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax) { - word0->link = link->link; - } - } - } +void TextPage::unrotateWords(GList *words, int rot) { + TextWord *word; + double xMin, yMin, xMax, yMax; + int i, j; + + switch (rot) { + case 0: + default: + // no transform + break; + case 1: + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + xMin = pageWidth - word->yMax; + xMax = pageWidth - word->yMin; + yMin = word->xMin; + yMax = word->xMax; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + word->rot = (word->rot + 1) & 3; + } + break; + case 2: + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + xMin = pageWidth - word->xMax; + xMax = pageWidth - word->xMin; + yMin = pageHeight - word->yMax; + yMax = pageHeight - word->yMin; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + word->rot = (word->rot + 2) & 3; + for (j = 0; j <= word->len; ++j) { + word->edge[j] = pageWidth - word->edge[j]; } } + break; + case 3: + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + xMin = word->yMin; + xMax = word->yMax; + yMin = pageHeight - word->xMax; + yMax = pageHeight - word->xMin; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + word->rot = (word->rot + 3) & 3; + for (j = 0; j <= word->len; ++j) { + word->edge[j] = pageHeight - word->edge[j]; + } + } + break; } +} - //----- assemble the blocks +// Determine the primary text direction (LR vs RL). Returns true for +// LR, false for RL. +GBool TextPage::checkPrimaryLR(GList *charsA) { + TextChar *ch; + int i, lrCount; - //~ add an outer loop for writing mode (vertical text) + lrCount = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (unicodeTypeL(ch->c)) { + ++lrCount; + } else if (unicodeTypeR(ch->c)) { + --lrCount; + } + } + return lrCount >= 0; +} - // build blocks for each rotation value - for (rot = 0; rot < 4; ++rot) { - pool = pools[rot]; - poolMinBaseIdx = pool->minBaseIdx; - count[rot] = 0; - - // add blocks until no more words are left - while (1) { - - // find the first non-empty line in the pool - for (; - poolMinBaseIdx <= pool->maxBaseIdx && - !pool->getPool(poolMinBaseIdx); - ++poolMinBaseIdx) ; - if (poolMinBaseIdx > pool->maxBaseIdx) { - break; - } +// Remove duplicate characters. The list of chars has been sorted -- +// by x for rot=0,2; by y for rot=1,3. +void TextPage::removeDuplicates(GList *charsA, int rot) { + TextChar *ch, *ch2; + double xDelta, yDelta; + int i, j; - // look for the left-most word in the first four lines of the - // pool -- this avoids starting with a superscript word - startBaseIdx = poolMinBaseIdx; - for (baseIdx = poolMinBaseIdx + 1; - baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx; - ++baseIdx) { - if (!pool->getPool(baseIdx)) { - continue; + if (rot & 1) { + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xDelta = dupMaxSecDelta * ch->fontSize; + yDelta = dupMaxPriDelta * ch->fontSize; + j = i + 1; + while (j < charsA->getLength()) { + ch2 = (TextChar *)charsA->get(j); + if (ch2->yMin - ch->yMin >= yDelta) { + break; } - if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx)) - < 0) { - startBaseIdx = baseIdx; + if (ch2->c == ch->c && + fabs(ch2->xMin - ch->xMin) < xDelta && + fabs(ch2->xMax - ch->xMax) < xDelta && + fabs(ch2->yMax - ch->yMax) < yDelta) { + charsA->del(j); + } else { + ++j; } } - - // create a new block - word0 = pool->getPool(startBaseIdx); - pool->setPool(startBaseIdx, word0->next); - word0->next = NULL; - blk = new TextBlock(this, rot); - blk->addWord(word0); - - fontSize = word0->fontSize; - minBase = maxBase = word0->base; - colSpace1 = minColSpacing1 * fontSize; - colSpace2 = minColSpacing2 * fontSize; - lineSpace = maxLineSpacingDelta * fontSize; - intraLineSpace = maxIntraLineDelta * fontSize; - - // add words to the block - do { - found = gFalse; - - // look for words on the line above the current top edge of - // the block - newMinBase = minBase; - for (baseIdx = pool->getBaseIdx(minBase); - baseIdx >= pool->getBaseIdx(minBase - lineSpace); - --baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base < minBase && - word1->base >= minBase - lineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin) - : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta1 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - found = gTrue; - newMinBase = word2->base; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - minBase = newMinBase; - - // look for words on the line below the current bottom edge of - // the block - newMaxBase = maxBase; - for (baseIdx = pool->getBaseIdx(maxBase); - baseIdx <= pool->getBaseIdx(maxBase + lineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base > maxBase && - word1->base <= maxBase + lineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin) - : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta1 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - found = gTrue; - newMaxBase = word2->base; - } else { - word0 = word1; - word1 = word1->next; - } - } + } + } else { + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xDelta = dupMaxPriDelta * ch->fontSize; + yDelta = dupMaxSecDelta * ch->fontSize; + j = i + 1; + while (j < charsA->getLength()) { + ch2 = (TextChar *)charsA->get(j); + if (ch2->xMin - ch->xMin >= xDelta) { + break; } - maxBase = newMaxBase; - - // look for words that are on lines already in the block, and - // that overlap the block horizontally - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax + colSpace1 && - word1->xMax > blk->xMin - colSpace1) - : (word1->yMin < blk->yMax + colSpace1 && - word1->yMax > blk->yMin - colSpace1)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta2 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - found = gTrue; - } else { - word0 = word1; - word1 = word1->next; - } - } + if (ch2->c == ch->c && + fabs(ch2->xMax - ch->xMax) < xDelta && + fabs(ch2->yMin - ch->yMin) < yDelta && + fabs(ch2->yMax - ch->yMax) < yDelta) { + charsA->del(j); + } else { + ++j; } + } + } + } +} - // only check for outlying words (the next two chunks of code) - // if we didn't find anything else - if (found) { - continue; - } +// Split the characters into trees of TextBlocks, one tree for each +// rotation. Merge into a single tree (with the primary rotation). +TextBlock *TextPage::splitChars(GList *charsA) { + TextBlock *tree[4]; + TextBlock *blk; + GList *chars2, *clippedChars; + TextChar *ch; + int rot, i; - // scan down the left side of the block, looking for words - // that are near (but not overlapping) the block; if there are - // three or fewer, add them to the block - n = 0; - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMax <= blk->xMin && - word1->xMax > blk->xMin - colSpace2) - : (word1->yMax <= blk->yMin && - word1->yMax > blk->yMin - colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - ++n; - break; - } - word1 = word1->next; - } - } - if (n > 0 && n <= 3) { - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMax <= blk->xMin && - word1->xMax > blk->xMin - colSpace2) - : (word1->yMax <= blk->yMin && - word1->yMax > blk->yMin - colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - if (word2->base < minBase) { - minBase = word2->base; - } else if (word2->base > maxBase) { - maxBase = word2->base; - } - found = gTrue; - break; - } else { - word0 = word1; - word1 = word1->next; - } - } + // split: build a tree of TextBlocks for each rotation + clippedChars = new GList(); + for (rot = 0; rot < 4; ++rot) { + chars2 = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->rot == rot) { + chars2->append(ch); + } + } + tree[rot] = NULL; + if (chars2->getLength() > 0) { + chars2->sort((rot & 1) ? &TextChar::cmpY : &TextChar::cmpX); + removeDuplicates(chars2, rot); + if (control.clipText) { + i = 0; + while (i < chars2->getLength()) { + ch = (TextChar *)chars2->get(i); + if (ch->clipped) { + ch = (TextChar *)chars2->del(i); + clippedChars->append(ch); + } else { + ++i; } } + } + if (chars2->getLength() > 0) { + tree[rot] = split(chars2, rot); + } + } + delete chars2; + } - // scan down the right side of the block, looking for words - // that are near (but not overlapping) the block; if there are - // three or fewer, add them to the block - n = 0; - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin >= blk->xMax && - word1->xMin < blk->xMax + colSpace2) - : (word1->yMin >= blk->yMax && - word1->yMin < blk->yMax + colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - ++n; - break; - } - word1 = word1->next; - } + // if the page contains no (unclipped) text, just leave an empty + // column list + if (!tree[0]) { + delete clippedChars; + return NULL; + } + + // if the main tree is not a multicolumn node, insert one so that + // rotated text has somewhere to go + if (tree[0]->tag != blkTagMulticolumn) { + blk = new TextBlock(blkHorizSplit, 0); + blk->addChild(tree[0]); + blk->tag = blkTagMulticolumn; + tree[0] = blk; + } + + // merge non-primary-rotation text into the primary-rotation tree + for (rot = 1; rot < 4; ++rot) { + if (tree[rot]) { + insertIntoTree(tree[rot], tree[0]); + tree[rot] = NULL; + } + } + + if (clippedChars->getLength()) { + insertClippedChars(clippedChars, tree[0]); + } + delete clippedChars; + +#if 0 //~debug + dumpTree(tree[0]); +#endif + + return tree[0]; +} + +// Generate a tree of TextBlocks, marked as columns, lines, and words. +TextBlock *TextPage::split(GList *charsA, int rot) { + TextBlock *blk; + GList *chars2, *chars3; + int *horizProfile, *vertProfile; + double xMin, yMin, xMax, yMax; + int xMinI, yMinI, xMaxI, yMaxI; + int xMinI2, yMinI2, xMaxI2, yMaxI2; + TextChar *ch; + double minFontSize, avgFontSize, splitPrecision; + double nLines, vertGapThreshold, ascentAdjust, descentAdjust, minChunk; + int horizGapSize, vertGapSize; + double horizGapSize2, vertGapSize2; + int minHorizChunkWidth, minVertChunkWidth, nHorizGaps, nVertGaps; + double largeCharSize; + int nLargeChars; + GBool doHorizSplit, doVertSplit, smallSplit; + int i, x, y, prev, start; + + //----- compute bbox, min font size, average font size, and + // split precision for this block + + xMin = yMin = xMax = yMax = 0; // make gcc happy + minFontSize = avgFontSize = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (i == 0 || ch->xMin < xMin) { + xMin = ch->xMin; + } + if (i == 0 || ch->yMin < yMin) { + yMin = ch->yMin; + } + if (i == 0 || ch->xMax > xMax) { + xMax = ch->xMax; + } + if (i == 0 || ch->yMax > yMax) { + yMax = ch->yMax; + } + avgFontSize += ch->fontSize; + if (i == 0 || ch->fontSize < minFontSize) { + minFontSize = ch->fontSize; + } + } + avgFontSize /= charsA->getLength(); + splitPrecision = splitPrecisionMul * minFontSize; + if (splitPrecision < minSplitPrecision) { + splitPrecision = minSplitPrecision; + } + + //----- compute the horizontal and vertical profiles + + // add some slack to the array bounds to avoid floating point + // precision problems + xMinI = (int)floor(xMin / splitPrecision) - 1; + yMinI = (int)floor(yMin / splitPrecision) - 1; + xMaxI = (int)floor(xMax / splitPrecision) + 1; + yMaxI = (int)floor(yMax / splitPrecision) + 1; + horizProfile = (int *)gmallocn(yMaxI - yMinI + 1, sizeof(int)); + vertProfile = (int *)gmallocn(xMaxI - xMinI + 1, sizeof(int)); + memset(horizProfile, 0, (yMaxI - yMinI + 1) * sizeof(int)); + memset(vertProfile, 0, (xMaxI - xMinI + 1) * sizeof(int)); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + // yMinI2 and yMaxI2 are adjusted to allow for slightly overlapping lines + switch (rot) { + case 0: + default: + xMinI2 = (int)floor(ch->xMin / splitPrecision); + xMaxI2 = (int)floor(ch->xMax / splitPrecision); + ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin); + yMinI2 = (int)floor((ch->yMin + ascentAdjust) / splitPrecision); + descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin); + yMaxI2 = (int)floor((ch->yMax - descentAdjust) / splitPrecision); + break; + case 1: + descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin); + xMinI2 = (int)floor((ch->xMin + descentAdjust) / splitPrecision); + ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin); + xMaxI2 = (int)floor((ch->xMax - ascentAdjust) / splitPrecision); + yMinI2 = (int)floor(ch->yMin / splitPrecision); + yMaxI2 = (int)floor(ch->yMax / splitPrecision); + break; + case 2: + xMinI2 = (int)floor(ch->xMin / splitPrecision); + xMaxI2 = (int)floor(ch->xMax / splitPrecision); + descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin); + yMinI2 = (int)floor((ch->yMin + descentAdjust) / splitPrecision); + ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin); + yMaxI2 = (int)floor((ch->yMax - ascentAdjust) / splitPrecision); + break; + case 3: + ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin); + xMinI2 = (int)floor((ch->xMin + ascentAdjust) / splitPrecision); + descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin); + xMaxI2 = (int)floor((ch->xMax - descentAdjust) / splitPrecision); + yMinI2 = (int)floor(ch->yMin / splitPrecision); + yMaxI2 = (int)floor(ch->yMax / splitPrecision); + break; + } + for (y = yMinI2; y <= yMaxI2; ++y) { + ++horizProfile[y - yMinI]; + } + for (x = xMinI2; x <= xMaxI2; ++x) { + ++vertProfile[x - xMinI]; + } + } + + //----- find the largest gaps in the horizontal and vertical profiles + + horizGapSize = 0; + for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ; + for (y = start; y < yMaxI; ++y) { + if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) { + start = y; + } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) { + if (y - start > horizGapSize) { + horizGapSize = y - start; + } + } + } + vertGapSize = 0; + for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; + for (x = start; x < xMaxI; ++x) { + if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) { + start = x; + } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) { + if (x - start > vertGapSize) { + vertGapSize = x - start; + } + } + } + horizGapSize2 = horizGapSize - splitGapSlack * avgFontSize / splitPrecision; + if (horizGapSize2 < 0.99) { + horizGapSize2 = 0.99; + } + vertGapSize2 = vertGapSize - splitGapSlack * avgFontSize / splitPrecision; + if (vertGapSize2 < 0.99) { + vertGapSize2 = 0.99; + } + + //----- count horiz/vert gaps equivalent to largest gaps + + minHorizChunkWidth = yMaxI - yMinI; + nHorizGaps = 0; + for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ; + prev = start - 1; + for (y = start; y < yMaxI; ++y) { + if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) { + start = y; + } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) { + if (y - start > horizGapSize2) { + ++nHorizGaps; + if (start - prev < minHorizChunkWidth) { + minHorizChunkWidth = start - prev; } - if (n > 0 && n <= 3) { - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin >= blk->xMax && - word1->xMin < blk->xMax + colSpace2) - : (word1->yMin >= blk->yMax && - word1->yMin < blk->yMax + colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - if (word2->base < minBase) { - minBase = word2->base; - } else if (word2->base > maxBase) { - maxBase = word2->base; - } - found = gTrue; - break; - } else { - word0 = word1; - word1 = word1->next; - } - } - } + prev = y; + } + } + } + minVertChunkWidth = xMaxI - xMinI; + nVertGaps = 0; + for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; + prev = start - 1; + for (x = start; x < xMaxI; ++x) { + if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) { + start = x; + } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) { + if (x - start > vertGapSize2) { + ++nVertGaps; + if (start - prev < minVertChunkWidth) { + minVertChunkWidth = start - prev; } + prev = x; + } + } + } - } while (found); + //----- compute splitting parameters - //~ need to compute the primary writing mode (horiz/vert) in - //~ addition to primary rotation + // approximation of number of lines in block + if (fabs(avgFontSize) < 0.001) { + nLines = 1; + } else if (rot & 1) { + nLines = (xMax - xMin) / avgFontSize; + } else { + nLines = (yMax - yMin) / avgFontSize; + } + + // compute the minimum allowed vertical gap size + // (this is a horizontal gap threshold for rot=1,3 + if (control.mode == textOutTableLayout) { + vertGapThreshold = vertGapThresholdTableMax + + vertGapThresholdTableSlope * nLines; + if (vertGapThreshold < vertGapThresholdTableMin) { + vertGapThreshold = vertGapThresholdTableMin; + } + } else { + vertGapThreshold = vertGapThresholdMax + vertGapThresholdSlope * nLines; + if (vertGapThreshold < vertGapThresholdMin) { + vertGapThreshold = vertGapThresholdMin; + } + } + vertGapThreshold = vertGapThreshold * avgFontSize / splitPrecision; + + // compute the minimum allowed chunk width + if (control.mode == textOutTableLayout) { + minChunk = 0; + } else { + minChunk = vertSplitChunkThreshold * avgFontSize / splitPrecision; + } + + // look for large chars + // -- this kludge (multiply by 256, convert to int, divide by 256.0) + // prevents floating point stability issues on x86 with gcc, where + // largeCharSize could otherwise have slightly different values + // here and where it's used below to do the large char partition + // (because it gets truncated from 80 to 64 bits when spilled) + largeCharSize = (int)(largeCharThreshold * avgFontSize * 256) / 256.0; + nLargeChars = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->fontSize > largeCharSize) { + ++nLargeChars; + } + } + + // figure out which type of split to do + doHorizSplit = doVertSplit = gFalse; + smallSplit = gFalse; + if (rot & 1) { + if (nHorizGaps > 0 && + (horizGapSize > vertGapSize || control.mode == textOutTableLayout) && + horizGapSize > vertGapThreshold && + minHorizChunkWidth > minChunk) { + doHorizSplit = gTrue; + } else if (nVertGaps > 0) { + doVertSplit = gTrue; + } else if (nLargeChars == 0 && nHorizGaps > 0) { + doHorizSplit = gTrue; + smallSplit = gTrue; + } + } else { + if (nVertGaps > 0 && + (vertGapSize > horizGapSize || control.mode == textOutTableLayout) && + vertGapSize > vertGapThreshold && + minVertChunkWidth > minChunk) { + doVertSplit = gTrue; + } else if (nHorizGaps > 0) { + doHorizSplit = gTrue; + } else if (nLargeChars == 0 && nVertGaps > 0) { + doVertSplit = gTrue; + smallSplit = gTrue; + } + } - // coalesce the block, and add it to the list - blk->coalesce(uMap, fixedPitch); - if (lastBlk) { - lastBlk->next = blk; + //----- split the block + + //~ this could use "other content" (vector graphics, rotated text) -- + //~ presence of other content in a gap means we should definitely split + + // split vertically + if (doVertSplit) { + blk = new TextBlock(blkVertSplit, rot); + blk->smallSplit = smallSplit; + for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; + prev = start - 1; + for (x = start; x < xMaxI; ++x) { + if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) { + start = x; + } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) { + if (x - start > vertGapSize2) { + chars2 = getChars(charsA, (prev + 0.5) * splitPrecision, yMin - 1, + (start + 1.5) * splitPrecision, yMax + 1); + blk->addChild(split(chars2, rot)); + delete chars2; + prev = x; + } + } + } + chars2 = getChars(charsA, (prev + 0.5) * splitPrecision, yMin - 1, + xMax + 1, yMax + 1); + blk->addChild(split(chars2, rot)); + delete chars2; + + // split horizontally + } else if (doHorizSplit) { + blk = new TextBlock(blkHorizSplit, rot); + blk->smallSplit = smallSplit; + for (start = yMinI; + start < yMaxI && !horizProfile[start - yMinI]; + ++start) ; + prev = start - 1; + for (y = start; y < yMaxI; ++y) { + if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) { + start = y; + } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) { + if (y - start > horizGapSize2) { + chars2 = getChars(charsA, xMin - 1, (prev + 0.5) * splitPrecision, + xMax + 1, (start + 1.5) * splitPrecision); + blk->addChild(split(chars2, rot)); + delete chars2; + prev = y; + } + } + } + chars2 = getChars(charsA, xMin - 1, (prev + 0.5) * splitPrecision, + xMax + 1, yMax + 1); + blk->addChild(split(chars2, rot)); + delete chars2; + + // split into larger and smaller chars + } else if (nLargeChars > 0) { + chars2 = new GList(); + chars3 = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->fontSize > largeCharSize) { + chars2->append(ch); } else { - blkList = blk; + chars3->append(ch); } - lastBlk = blk; - count[rot] += blk->charCount; - ++nBlocks; } + blk = split(chars3, rot); + insertLargeChars(chars2, blk); + delete chars2; + delete chars3; - if (count[rot] > count[primaryRot]) { - primaryRot = rot; + // create a leaf node + } else { + blk = new TextBlock(blkLeaf, rot); + for (i = 0; i < charsA->getLength(); ++i) { + blk->addChild((TextChar *)charsA->get(i)); } } -#if 0 // for debugging - printf("*** rotation ***\n"); - for (rot = 0; rot < 4; ++rot) { - printf(" %d: %6d\n", rot, count[rot]); + gfree(horizProfile); + gfree(vertProfile); + + tagBlock(blk); + + return blk; +} + +// Return the subset of chars inside a rectangle. +GList *TextPage::getChars(GList *charsA, double xMin, double yMin, + double xMax, double yMax) { + GList *ret; + TextChar *ch; + double x, y; + int i; + + ret = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + // because of {ascent,descent}AdjustFactor, the y coords (or x + // coords for rot 1,3) for the gaps will be a little bit tight -- + // so we use the center of the character here + x = 0.5 * (ch->xMin + ch->xMax); + y = 0.5 * (ch->yMin + ch->yMax); + if (x > xMin && x < xMax && y > yMin && y < yMax) { + ret->append(ch); + } } - printf(" primary rot = %d\n", primaryRot); - printf("\n"); -#endif + return ret; +} -#if 0 // for debugging - printf("*** blocks ***\n"); - for (blk = blkList; blk; blk = blk->next) { - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax); - for (line = blk->lines; line; line = line->next) { - printf(" line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f\n", - line->xMin, line->xMax, line->yMin, line->yMax, line->base); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); +// Decide whether this block is a line, column, or multiple columns: +// - all leaf nodes are lines +// - horiz split nodes whose children are lines or columns are columns +// - other horiz split nodes are multiple columns +// - vert split nodes, with small gaps, whose children are lines are lines +// - other vert split nodes are multiple columns +// (for rot=1,3: the horiz and vert splits are swapped) +// In table layout mode: +// - all leaf nodes are lines +// - vert split nodes, with small gaps, whose children are lines are lines +// - everything else is multiple columns +void TextPage::tagBlock(TextBlock *blk) { + TextBlock *child; + int i; + + if (control.mode == textOutTableLayout) { + if (blk->type == blkLeaf) { + blk->tag = blkTagLine; + } else if (blk->type == ((blk->rot & 1) ? blkHorizSplit : blkVertSplit) && + blk->smallSplit) { + blk->tag = blkTagLine; + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (child->tag != blkTagLine) { + blk->tag = blkTagMulticolumn; + break; } - printf("'\n"); } + } else { + blk->tag = blkTagMulticolumn; } + return; } - printf("\n"); -#endif - // determine the primary direction - lrCount = 0; - for (blk = blkList; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word0 = line->words; word0; word0 = word0->next) { - for (i = 0; i < word0->len; ++i) { - if (unicodeTypeL(word0->text[i])) { - ++lrCount; - } else if (unicodeTypeR(word0->text[i])) { - --lrCount; + if (blk->type == blkLeaf) { + blk->tag = blkTagLine; + + } else { + if (blk->type == ((blk->rot & 1) ? blkVertSplit : blkHorizSplit)) { + blk->tag = blkTagColumn; + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (child->tag != blkTagColumn && child->tag != blkTagLine) { + blk->tag = blkTagMulticolumn; + break; + } + } + } else { + if (blk->smallSplit) { + blk->tag = blkTagLine; + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (child->tag != blkTagLine) { + blk->tag = blkTagMulticolumn; + break; } } + } else { + blk->tag = blkTagMulticolumn; } } } - primaryLR = lrCount >= 0; +} -#if 0 // for debugging - printf("*** direction ***\n"); - printf("lrCount = %d\n", lrCount); - printf("primaryLR = %d\n", primaryLR); -#endif +// Insert a list of large characters into a tree. +void TextPage::insertLargeChars(GList *largeChars, TextBlock *blk) { + TextChar *ch, *ch2; + GBool singleLine; + double xLimit, yLimit, minOverlap; + int i; - //----- column assignment + //~ this currently works only for characters in the primary rotation + + // check to see if the large chars are a single line, in the + // upper-left corner of blk (this is just a rough estimate) + xLimit = blk->xMin + 0.5 * (blk->xMin + blk->xMax); + yLimit = blk->yMin + 0.5 * (blk->yMin + blk->yMax); + singleLine = gTrue; + // note: largeChars are already sorted by x + for (i = 0; i < largeChars->getLength(); ++i) { + ch2 = (TextChar *)largeChars->get(i); + if (ch2->xMax > xLimit || ch2->yMax > yLimit) { + singleLine = gFalse; + break; + } + if (i > 0) { + ch = (TextChar *)largeChars->get(i-1); + minOverlap = 0.5 * (ch->fontSize < ch2->fontSize ? ch->fontSize + : ch2->fontSize); + if (ch->yMax - ch2->yMin < minOverlap || + ch2->yMax - ch->yMin < minOverlap) { + singleLine = gFalse; + break; + } + } + } - if (physLayout && fixedPitch) { + if (singleLine) { + // if the large chars are a single line, prepend them to the first + // leaf node in blk + insertLargeCharsInFirstLeaf(largeChars, blk); + } else { + // if the large chars are not a single line, prepend each one to + // the appropriate leaf node -- this handles cases like bullets + // drawn in a large font, on the left edge of a column + for (i = largeChars->getLength() - 1; i >= 0; --i) { + ch = (TextChar *)largeChars->get(i); + insertLargeCharInLeaf(ch, blk); + } + } +} - blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - for (blk = blkList, i = 0; blk; blk = blk->next, ++i) { - blocks[i] = blk; - col1 = 0; // make gcc happy - switch (primaryRot) { - case 0: - col1 = (int)(blk->xMin / fixedPitch + 0.5); - break; - case 1: - col1 = (int)(blk->yMin / fixedPitch + 0.5); - break; - case 2: - col1 = (int)((pageWidth - blk->xMax) / fixedPitch + 0.5); - break; - case 3: - col1 = (int)((pageHeight - blk->yMax) / fixedPitch + 0.5); +// Find the first leaf (in depth-first order) in blk, and prepend a +// list of large chars. +void TextPage::insertLargeCharsInFirstLeaf(GList *largeChars, TextBlock *blk) { + TextChar *ch; + int i; + + if (blk->type == blkLeaf) { + for (i = largeChars->getLength() - 1; i >= 0; --i) { + ch = (TextChar *)largeChars->get(i); + blk->prependChild(ch); + } + } else { + insertLargeCharsInFirstLeaf(largeChars, (TextBlock *)blk->children->get(0)); + blk->updateBounds(0); + } +} + +// Find the leaf in <blk> where large char <ch> belongs, and prepend +// it. +void TextPage::insertLargeCharInLeaf(TextChar *ch, TextBlock *blk) { + TextBlock *child; + double y; + int i; + + //~ this currently works only for characters in the primary rotation + + //~ this currently just looks down the left edge of blk + //~ -- it could be extended to do more + + // estimate the baseline of ch + y = ch->yMin + 0.75 * (ch->yMax - ch->yMin); + + if (blk->type == blkLeaf) { + blk->prependChild(ch); + } else if (blk->type == blkHorizSplit) { + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (y < child->yMax || i == blk->children->getLength() - 1) { + insertLargeCharInLeaf(ch, child); + blk->updateBounds(i); break; } - blk->col = col1; - for (line = blk->lines; line; line = line->next) { - for (j = 0; j <= line->len; ++j) { - line->col[j] += col1; + } + } else { + insertLargeCharInLeaf(ch, (TextBlock *)blk->children->get(0)); + blk->updateBounds(0); + } +} + +// Merge blk (rot != 0) into primaryTree (rot == 0). +void TextPage::insertIntoTree(TextBlock *blk, TextBlock *primaryTree) { + TextBlock *child; + + // we insert a whole column at a time - so call insertIntoTree + // recursively until we get to a column (or line) + + if (blk->tag == blkTagMulticolumn) { + while (blk->children->getLength()) { + child = (TextBlock *)blk->children->del(0); + insertIntoTree(child, primaryTree); + } + delete blk; + } else { + insertColumnIntoTree(blk, primaryTree); + } +} + +// Insert a column (as an atomic subtree) into tree. +// Requirement: tree is not a leaf node. +void TextPage::insertColumnIntoTree(TextBlock *column, TextBlock *tree) { + TextBlock *child; + int i; + + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (child->tag == blkTagMulticolumn && + column->xMin >= child->xMin && + column->yMin >= child->yMin && + column->xMax <= child->xMax && + column->yMax <= child->yMax) { + insertColumnIntoTree(column, child); + tree->tag = blkTagMulticolumn; + return; + } + } + + if (tree->type == blkVertSplit) { + if (tree->rot == 1 || tree->rot == 2) { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->xMax > 0.5 * (child->xMin + child->xMax)) { + break; + } + } + } else { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->xMin < 0.5 * (child->xMin + child->xMax)) { + break; } } } + } else if (tree->type == blkHorizSplit) { + if (tree->rot >= 2) { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->yMax > 0.5 * (child->yMin + child->yMax)) { + break; + } + } + } else { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->yMin < 0.5 * (child->yMin + child->yMax)) { + break; + } + } + } + } else { + // this should never happen + return; + } + tree->children->insert(i, column); + tree->tag = blkTagMulticolumn; +} +// Insert clipped characters back into the TextBlock tree. +void TextPage::insertClippedChars(GList *clippedChars, TextBlock *tree) { + TextChar *ch, *ch2; + TextBlock *leaf; + double y; + int i; + + //~ this currently works only for characters in the primary rotation + + clippedChars->sort(TextChar::cmpX); + while (clippedChars->getLength()) { + ch = (TextChar *)clippedChars->del(0); + if (ch->rot != 0) { + continue; + } + if (!(leaf = findClippedCharLeaf(ch, tree))) { + continue; + } + leaf->addChild(ch); + i = 0; + while (i < clippedChars->getLength()) { + ch2 = (TextChar *)clippedChars->get(i); + if (ch2->xMin > ch->xMax + clippedTextMaxWordSpace * ch->fontSize) { + break; + } + y = 0.5 * (ch2->yMin + ch2->yMax); + if (y > leaf->yMin && y < leaf->yMax) { + ch2 = (TextChar *)clippedChars->del(i); + leaf->addChild(ch2); + ch = ch2; + } else { + ++i; + } + } + } +} + +// Find the leaf in <tree> to which clipped char <ch> can be appended. +// Returns NULL if there is no appropriate append point. +TextBlock *TextPage::findClippedCharLeaf(TextChar *ch, TextBlock *tree) { + TextBlock *ret, *child; + double y; + int i; + + //~ this currently works only for characters in the primary rotation + + y = 0.5 * (ch->yMin + ch->yMax); + if (tree->type == blkLeaf) { + if (tree->rot == 0) { + if (y > tree->yMin && y < tree->yMax && + ch->xMin <= tree->xMax + clippedTextMaxWordSpace * ch->fontSize) { + return tree; + } + } } else { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if ((ret = findClippedCharLeaf(ch, child))) { + return ret; + } + } + } + return NULL; +} + +// Convert the tree of TextBlocks into a list of TextColumns. +GList *TextPage::buildColumns(TextBlock *tree) { + GList *columns; + + columns = new GList(); + buildColumns2(tree, columns); + return columns; +} + +void TextPage::buildColumns2(TextBlock *blk, GList *columns) { + TextColumn *col; + int i; - // sort blocks into xy order for column assignment - blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - for (blk = blkList, i = 0; blk; blk = blk->next, ++i) { - blocks[i] = blk; + switch (blk->tag) { + case blkTagLine: + case blkTagColumn: + col = buildColumn(blk); + columns->append(col); + break; + case blkTagMulticolumn: + for (i = 0; i < blk->children->getLength(); ++i) { + buildColumns2((TextBlock *)blk->children->get(i), columns); } - qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpXYPrimaryRot); + break; + } +} - // column assignment - for (i = 0; i < nBlocks; ++i) { - blk0 = blocks[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - blk1 = blocks[j]; - col2 = 0; // make gcc happy - switch (primaryRot) { - case 0: - if (blk0->xMin > blk1->xMax) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->xMax == blk1->xMin) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->xMin - blk1->xMin) / - (blk1->xMax - blk1->xMin)) * - blk1->nColumns); +TextColumn *TextPage::buildColumn(TextBlock *blk) { + GList *lines, *parLines; + GList *paragraphs; + TextLine *line0, *line1; + double spaceThresh, indent0, indent1, fontSize0, fontSize1; + int i; + + lines = new GList(); + buildLines(blk, lines); + + spaceThresh = paragraphSpacingThreshold * getAverageLineSpacing(lines); + + //~ could look for bulleted lists here: look for the case where + //~ all out-dented lines start with the same char + + // build the paragraphs + paragraphs = new GList(); + i = 0; + while (i < lines->getLength()) { + + // get the first line of the paragraph + parLines = new GList(); + line0 = (TextLine *)lines->get(i); + parLines->append(line0); + ++i; + + if (i < lines->getLength()) { + line1 = (TextLine *)lines->get(i); + indent0 = getLineIndent(line0, blk); + indent1 = getLineIndent(line1, blk); + fontSize0 = line0->fontSize; + fontSize1 = line1->fontSize; + + // inverted indent + if (indent1 - indent0 > minParagraphIndent * fontSize0 && + fabs(fontSize0 - fontSize1) <= paragraphFontSizeDelta && + getLineSpacing(line0, line1) <= spaceThresh) { + parLines->append(line1); + indent0 = indent1; + for (++i; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + fontSize1 = line1->fontSize; + if (indent0 - indent1 > minParagraphIndent * fontSize0) { + break; } - break; - case 1: - if (blk0->yMin > blk1->yMax) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->yMax == blk1->yMin) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->yMin - blk1->yMin) / - (blk1->yMax - blk1->yMin)) * - blk1->nColumns); + if (fabs(fontSize0 - fontSize1) > paragraphFontSizeDelta) { + break; } - break; - case 2: - if (blk0->xMax < blk1->xMin) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->xMin == blk1->xMax) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->xMax - blk1->xMax) / - (blk1->xMin - blk1->xMax)) * - blk1->nColumns); + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; } - break; - case 3: - if (blk0->yMax < blk1->yMin) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->yMin == blk1->yMax) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->yMax - blk1->yMax) / - (blk1->yMin - blk1->yMax)) * - blk1->nColumns); + parLines->append(line1); + } + + // drop cap + } else if (fontSize0 > largeCharThreshold * fontSize1 && + indent1 - indent0 > minParagraphIndent * fontSize1 && + getLineSpacing(line0, line1) < 0) { + parLines->append(line1); + fontSize0 = fontSize1; + for (++i; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + if (indent1 - indent0 <= minParagraphIndent * fontSize0) { + break; } - break; + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; + } + parLines->append(line1); } - if (col2 > col1) { - col1 = col2; + for (; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + fontSize1 = line1->fontSize; + if (indent1 - indent0 > minParagraphIndent * fontSize0) { + break; + } + if (fabs(fontSize0 - fontSize1) > paragraphFontSizeDelta) { + break; + } + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; + } + parLines->append(line1); } - } - blk0->col = col1; - for (line = blk0->lines; line; line = line->next) { - for (j = 0; j <= line->len; ++j) { - line->col[j] += col1; + + // regular indent or no indent + } else if (fabs(fontSize0 - fontSize1) <= paragraphFontSizeDelta && + getLineSpacing(line0, line1) <= spaceThresh) { + parLines->append(line1); + indent0 = indent1; + for (++i; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + fontSize1 = line1->fontSize; + if (indent1 - indent0 > minParagraphIndent * fontSize0) { + break; + } + if (fabs(fontSize0 - fontSize1) > paragraphFontSizeDelta) { + break; + } + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; + } + parLines->append(line1); } } } + paragraphs->append(new TextParagraph(parLines)); } -#if 0 // for debugging - printf("*** blocks, after column assignment ***\n"); - for (blk = blkList; blk; blk = blk->next) { - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f col=%d nCols=%d\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->col, - blk->nColumns); - for (line = blk->lines; line; line = line->next) { - printf(" line: col[0]=%d\n", line->col[0]); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); - } - printf("'\n"); - } + delete lines; + + return new TextColumn(paragraphs, blk->xMin, blk->yMin, + blk->xMax, blk->yMax); +} + +double TextPage::getLineIndent(TextLine *line, TextBlock *blk) { + double indent; + + switch (line->rot) { + case 0: + default: indent = line->xMin - blk->xMin; break; + case 1: indent = line->yMin - blk->yMin; break; + case 2: indent = blk->xMax - line->xMax; break; + case 3: indent = blk->yMax - line->yMax; break; + } + return indent; +} + +// Compute average line spacing in column. +double TextPage::getAverageLineSpacing(GList *lines) { + double avg, sp; + int n, i; + + avg = 0; + n = 0; + for (i = 1; i < lines->getLength(); ++i) { + sp = getLineSpacing((TextLine *)lines->get(i - 1), + (TextLine *)lines->get(i)); + if (sp > 0) { + avg += sp; + ++n; } } - printf("\n"); -#endif + if (n > 0) { + avg /= n; + } + return avg; +} + +// Compute the space between two lines. +double TextPage::getLineSpacing(TextLine *line0, TextLine *line1) { + double sp; + + switch (line0->rot) { + case 0: + default: sp = line1->yMin - line0->yMax; break; + case 1: sp = line0->xMin - line1->xMax; break; + case 2: sp = line0->yMin - line1->yMin; break; + case 3: sp = line1->xMin - line1->xMax; break; + } + return sp; +} + +void TextPage::buildLines(TextBlock *blk, GList *lines) { + TextLine *line; + int i; + + switch (blk->tag) { + case blkTagLine: + line = buildLine(blk); + if (blk->rot == 1 || blk->rot == 2) { + lines->insert(0, line); + } else { + lines->append(line); + } + break; + case blkTagColumn: + case blkTagMulticolumn: // multicolumn should never happen here + for (i = 0; i < blk->children->getLength(); ++i) { + buildLines((TextBlock *)blk->children->get(i), lines); + } + break; + } +} + +TextLine *TextPage::buildLine(TextBlock *blk) { + GList *charsA; + GList *words; + TextChar *ch, *ch2; + TextWord *word; + double wordSp, lineFontSize, sp; + GBool spaceAfter, spaceAfter2; + int i, j; - //----- reading order sort + charsA = new GList(); + getLineChars(blk, charsA); - // sort blocks into yx order (in preparation for reading order sort) - qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpYXPrimaryRot); + wordSp = computeWordSpacingThreshold(charsA, blk->rot); - // compute space on left and right sides of each block - for (i = 0; i < nBlocks; ++i) { - blk0 = blocks[i]; - for (j = 0; j < nBlocks; ++j) { - blk1 = blocks[j]; - if (blk1 != blk0) { - blk0->updatePriMinMax(blk1); + words = new GList(); + lineFontSize = 0; + spaceAfter = gFalse; + i = 0; + while (i < charsA->getLength()) { + sp = wordSp - 1; + for (j = i+1; j < charsA->getLength(); ++j) { + ch = (TextChar *)charsA->get(j-1); + ch2 = (TextChar *)charsA->get(j); + sp = (blk->rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax); + if (sp > wordSp || + ch->font != ch2->font || + fabs(ch->fontSize - ch2->fontSize) > 0.01 || + (control.mode == textOutRawOrder && + ch2->charPos != ch->charPos + ch->charLen)) { + break; } + sp = wordSp - 1; + } + spaceAfter2 = spaceAfter; + spaceAfter = sp > wordSp; + word = new TextWord(charsA, i, j - i, blk->rot, + (blk->rot >= 2) ? spaceAfter2 : spaceAfter); + i = j; + if (blk->rot >= 2) { + words->insert(0, word); + } else { + words->append(word); + } + if (i == 0 || word->fontSize > lineFontSize) { + lineFontSize = word->fontSize; } } -#if 0 // for debugging - printf("*** blocks, after yx sort ***\n"); - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f space=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, - blk->priMin, blk->priMax); - for (line = blk->lines; line; line = line->next) { - printf(" line:\n"); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (j = 0; j < word0->len; ++j) { - fputc(word0->text[j] & 0xff, stdout); - } - printf("'\n"); + delete charsA; + + return new TextLine(words, blk->xMin, blk->yMin, blk->xMax, blk->yMax, + lineFontSize); +} + +void TextPage::getLineChars(TextBlock *blk, GList *charsA) { + int i; + + if (blk->type == blkLeaf) { + charsA->append(blk->children); + } else { + for (i = 0; i < blk->children->getLength(); ++i) { + getLineChars((TextBlock *)blk->children->get(i), charsA); + } + } +} + +// Compute the inter-word spacing threshold for a line of chars. +// Spaces greater than this threshold will be considered inter-word +// spaces. +double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { + TextChar *ch, *ch2; + double avgFontSize, minSp, maxSp, sp; + int i; + + avgFontSize = 0; + minSp = maxSp = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + avgFontSize += ch->fontSize; + if (i < charsA->getLength() - 1) { + ch2 = (TextChar *)charsA->get(i+1); + sp = (rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax); + if (i == 0 || sp < minSp) { + minSp = sp; + } + if (sp > maxSp) { + maxSp = sp; } } } - printf("\n"); -#endif + avgFontSize /= charsA->getLength(); + if (minSp < 0) { + minSp = 0; + } - // build the flows - //~ this needs to be adjusted for writing mode (vertical text) - //~ this also needs to account for right-to-left column ordering - blkArray = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - memcpy(blkArray, blocks, nBlocks * sizeof(TextBlock *)); - flows = lastFlow = NULL; - firstBlkIdx = 0; - nBlocksLeft = nBlocks; - while (nBlocksLeft > 0) { - - // find the upper-left-most block - for (; !blkArray[firstBlkIdx]; ++firstBlkIdx) ; - i = firstBlkIdx; - blk = blkArray[i]; - for (j = firstBlkIdx + 1; j < nBlocks; ++j) { - blk1 = blkArray[j]; - if (blk1) { - if (blk && blk->secondaryDelta(blk1) > 0) { - break; + // if spacing is completely uniform, assume it's a single word + // (technically it could be either "ABC" or "A B C", but it's + // essentially impossible to tell) + if (maxSp - minSp < uniformSpacing * avgFontSize) { + return maxSp + 1; + + // if there is some variation in spacing, but it's small, assume + // there are some inter-word spaces + } else if (maxSp - minSp < wordSpacing * avgFontSize) { + return 0.5 * (minSp + maxSp); + + // otherwise, assume a reasonable threshold for inter-word spacing + // (we can't use something like 0.5*(minSp+maxSp) here because there + // can be outliers at the high end) + } else { + return minSp + wordSpacing * avgFontSize; + } +} + +int TextPage::assignPhysLayoutPositions(GList *columns) { + assignLinePhysPositions(columns); + return assignColumnPhysPositions(columns); +} + +// Assign a physical x coordinate for each TextLine (relative to the +// containing TextColumn). This also computes TextColumn width and +// height. +void TextPage::assignLinePhysPositions(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + UnicodeMap *uMap; + int colIdx, parIdx, lineIdx; + + if (!(uMap = globalParams->getTextEncoding())) { + return; + } + + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + col->pw = col->ph = 0; + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + computeLinePhysWidth(line, uMap); + if (control.fixedPitch > 0) { + line->px = (int)((line->xMin - col->xMin) / control.fixedPitch); + } else if (fabs(line->fontSize) < 0.001) { + line->px = 0; + } else { + line->px = (int)((line->xMin - col->xMin) / + (physLayoutSpaceWidth * line->fontSize)); } - if (blk1->primaryCmp(blk) < 0) { - i = j; - blk = blk1; + if (line->px + line->pw > col->pw) { + col->pw = line->px + line->pw; } } + col->ph += par->lines->getLength(); } - blkArray[i] = NULL; - --nBlocksLeft; - blk->next = NULL; + col->ph += col->paragraphs->getLength() - 1; + } - // create a new flow, starting with the upper-left-most block - flow = new TextFlow(this, blk); - if (lastFlow) { - lastFlow->next = flow; + uMap->decRefCnt(); +} + +void TextPage::computeLinePhysWidth(TextLine *line, UnicodeMap *uMap) { + char buf[8]; + int n, i; + + if (uMap->isUnicode()) { + line->pw = line->len; + } else { + line->pw = 0; + for (i = 0; i < line->len; ++i) { + n = uMap->mapUnicode(line->text[i], buf, sizeof(buf)); + line->pw += n; + } + } +} + +// Assign physical x and y coordinates for each TextColumn. Returns +// the text height (max physical y + 1). +int TextPage::assignColumnPhysPositions(GList *columns) { + TextColumn *col, *col2; + double slack, xOverlap, yOverlap; + int ph, i, j; + + if (control.mode == textOutTableLayout) { + slack = tableCellOverlapSlack; + } else { + slack = 0; + } + + // assign x positions + columns->sort(&TextColumn::cmpX); + for (i = 0; i < columns->getLength(); ++i) { + col = (TextColumn *)columns->get(i); + if (control.fixedPitch) { + col->px = (int)(col->xMin / control.fixedPitch); } else { - flows = flow; - } - lastFlow = flow; - fontSize = blk->lines->words->fontSize; - - // push the upper-left-most block on the stack - blk->stackNext = NULL; - blkStack = blk; - - // find the other blocks in this flow - while (blkStack) { - - // find the upper-left-most block under (but within - // maxBlockSpacing of) the top block on the stack - blkSpace = maxBlockSpacing * blkStack->lines->words->fontSize; - blk = NULL; - i = -1; - for (j = firstBlkIdx; j < nBlocks; ++j) { - blk1 = blkArray[j]; - if (blk1) { - if (blkStack->secondaryDelta(blk1) > blkSpace) { - break; - } - if (blk && blk->secondaryDelta(blk1) > 0) { - break; + col->px = 0; + for (j = 0; j < i; ++j) { + col2 = (TextColumn *)columns->get(j); + xOverlap = col2->xMax - col->xMin; + if (xOverlap < slack * (col2->xMax - col2->xMin)) { + if (col2->px + col2->pw + 2 > col->px) { + col->px = col2->px + col2->pw + 2; } - if (blk1->isBelow(blkStack) && - (!blk || blk1->primaryCmp(blk) < 0)) { - i = j; - blk = blk1; + } else { + yOverlap = (col->yMax < col2->yMax ? col->yMax : col2->yMax) - + (col->yMin > col2->yMin ? col->yMin : col2->yMin); + if (yOverlap > 0 && xOverlap < yOverlap) { + if (col2->px + col2->pw > col->px) { + col->px = col2->px + col2->pw; + } + } else { + if (col2->px > col->px) { + col->px = col2->px; + } } } } + } + } - // if a suitable block was found, add it to the flow and push it - // onto the stack - if (blk && flow->blockFits(blk, blkStack)) { - blkArray[i] = NULL; - --nBlocksLeft; - blk->next = NULL; - flow->addBlock(blk); - fontSize = blk->lines->words->fontSize; - blk->stackNext = blkStack; - blkStack = blk; - - // otherwise (if there is no block under the top block or the - // block is not suitable), pop the stack + // assign y positions + ph = 0; + columns->sort(&TextColumn::cmpY); + for (i = 0; i < columns->getLength(); ++i) { + col = (TextColumn *)columns->get(i); + col->py = 0; + for (j = 0; j < i; ++j) { + col2 = (TextColumn *)columns->get(j); + yOverlap = col2->yMax - col->yMin; + if (yOverlap < slack * (col2->yMax - col2->yMin)) { + if (col2->py + col2->ph + 1 > col->py) { + col->py = col2->py + col2->ph + 1; + } } else { - blkStack = blkStack->stackNext; + xOverlap = (col->xMax < col2->xMax ? col->xMax : col2->xMax) - + (col->xMin > col2->xMin ? col->xMin : col2->xMin); + if (xOverlap > 0 && yOverlap < xOverlap) { + if (col2->py + col2->ph > col->py) { + col->py = col2->py + col2->ph; + } + } else { + if (col2->py > col->py) { + col->py = col2->py; + } + } } } + if (col->py + col->ph > ph) { + ph = col->py + col->ph; + } } - gfree(blkArray); - -#if 0 // for debugging - printf("*** flows ***\n"); - for (flow = flows; flow; flow = flow->next) { - printf("flow: x=%.2f..%.2f y=%.2f..%.2f pri:%.2f..%.2f\n", - flow->xMin, flow->xMax, flow->yMin, flow->yMax, - flow->priMin, flow->priMax); - for (blk = flow->blocks; blk; blk = blk->next) { - printf(" block: rot=%d x=%.2f..%.2f y=%.2f..%.2f pri=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, - blk->priMin, blk->priMax); - for (line = blk->lines; line; line = line->next) { - printf(" line:\n"); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); + + return ph; +} + +void TextPage::generateUnderlinesAndLinks(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word; + TextUnderline *underline; + TextLink *link; + double base, uSlack, ubSlack, hSlack; + int colIdx, parIdx, lineIdx, wordIdx, i; + + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + base = word->getBaseline(); + uSlack = underlineSlack * word->fontSize; + ubSlack = underlineBaselineSlack * word->fontSize; + hSlack = hyperlinkSlack * word->fontSize; + + //----- handle underlining + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + if (underline->horiz) { + if (word->rot == 0 || word->rot == 2) { + if (fabs(underline->y0 - base) < ubSlack && + underline->x0 < word->xMin + uSlack && + word->xMax - uSlack < underline->x1) { + word->underlined = gTrue; + } + } + } else { + if (word->rot == 1 || word->rot == 3) { + if (fabs(underline->x0 - base) < ubSlack && + underline->y0 < word->yMin + uSlack && + word->yMax - uSlack < underline->y1) { + word->underlined = gTrue; + } + } + } + } + + //----- handle links + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + if (link->xMin < word->xMin + hSlack && + word->xMax - hSlack < link->xMax && + link->yMin < word->yMin + hSlack && + word->yMax - hSlack < link->yMax) { + word->link = link; + } } - printf("'\n"); } } } } - printf("\n"); -#endif - - if (uMap) { - uMap->decRefCnt(); - } } +//------------------------------------------------------------------------ +// TextPage: access +//------------------------------------------------------------------------ + GBool TextPage::findText(Unicode *s, int len, GBool startAtTop, GBool stopAtBottom, GBool startAtLast, GBool stopAtLast, @@ -3131,20 +3639,31 @@ GBool TextPage::findText(Unicode *s, int len, GBool wholeWord, double *xMin, double *yMin, double *xMax, double *yMax) { - TextBlock *blk; + TextBlock *tree; + TextColumn *column; + TextParagraph *par; TextLine *line; Unicode *s2, *txt; Unicode *p; - int txtSize, m, i, j, k; double xStart, yStart, xStop, yStop; double xMin0, yMin0, xMax0, yMax0; double xMin1, yMin1, xMax1, yMax1; GBool found; + int txtSize, m, rot, colIdx, parIdx, lineIdx, i, j, k; - //~ needs to handle right-to-left text + //~ need to handle right-to-left text - if (rawOrder) { - return gFalse; + if (!findCols) { + rot = rotateChars(chars); + if ((tree = splitChars(chars))) { + findCols = buildColumns(tree); + delete tree; + } else { + // no text + findCols = new GList(); + } + unrotateChars(chars, rot); + unrotateColumns(findCols, rot); } // convert the search string to uppercase @@ -3180,139 +3699,152 @@ GBool TextPage::findText(Unicode *s, int len, xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - for (i = backward ? nBlocks - 1 : 0; - backward ? i >= 0 : i < nBlocks; - i += backward ? -1 : 1) { - blk = blocks[i]; + for (colIdx = backward ? findCols->getLength() - 1 : 0; + backward ? colIdx >= 0 : colIdx < findCols->getLength(); + colIdx += backward ? -1 : 1) { + column = (TextColumn *)findCols->get(colIdx); - // check: is the block above the top limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the blocks won't be sorted in the useful order) - if (!startAtTop && primaryRot == 0 && - (backward ? blk->yMin > yStart : blk->yMax < yStart)) { + // check: is the column above the top limit? + if (!startAtTop && (backward ? column->yMin > yStart + : column->yMax < yStart)) { continue; } - // check: is the block below the bottom limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the blocks won't be sorted in the useful order) - if (!stopAtBottom && primaryRot == 0 && - (backward ? blk->yMax < yStop : blk->yMin > yStop)) { - break; + // check: is the column below the bottom limit? + if (!stopAtBottom && (backward ? column->yMax < yStop + : column->yMin > yStop)) { + continue; } - for (line = blk->lines; line; line = line->next) { + for (parIdx = backward ? column->paragraphs->getLength() - 1 : 0; + backward ? parIdx >= 0 : parIdx < column->paragraphs->getLength(); + parIdx += backward ? -1 : 1) { + par = (TextParagraph *)column->paragraphs->get(parIdx); - // check: is the line above the top limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the lines won't be sorted in the useful order) - if (!startAtTop && primaryRot == 0 && - (backward ? line->yMin > yStart : line->yMin < yStart)) { + // check: is the paragraph above the top limit? + if (!startAtTop && (backward ? par->yMin > yStart + : par->yMax < yStart)) { continue; } - // check: is the line below the bottom limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the lines won't be sorted in the useful order) - if (!stopAtBottom && primaryRot == 0 && - (backward ? line->yMin < yStop : line->yMin > yStop)) { + // check: is the paragraph below the bottom limit? + if (!stopAtBottom && (backward ? par->yMax < yStop + : par->yMin > yStop)) { continue; } - // convert the line to uppercase - m = line->len; - if (!caseSensitive) { - if (m > txtSize) { - txt = (Unicode *)greallocn(txt, m, sizeof(Unicode)); - txtSize = m; + for (lineIdx = backward ? par->lines->getLength() - 1 : 0; + backward ? lineIdx >= 0 : lineIdx < par->lines->getLength(); + lineIdx += backward ? -1 : 1) { + line = (TextLine *)par->lines->get(lineIdx); + + // check: is the line above the top limit? + if (!startAtTop && (backward ? line->yMin > yStart + : line->yMax < yStart)) { + continue; } - for (k = 0; k < m; ++k) { - txt[k] = unicodeToUpper(line->text[k]); + + // check: is the line below the bottom limit? + if (!stopAtBottom && (backward ? line->yMax < yStop + : line->yMin > yStop)) { + continue; } - } else { - txt = line->text; - } - // search each position in this line - j = backward ? m - len : 0; - p = txt + j; - while (backward ? j >= 0 : j <= m - len) { - if (!wholeWord || - ((j == 0 || !unicodeTypeAlphaNum(txt[j - 1])) && - (j + len == m || !unicodeTypeAlphaNum(txt[j + len])))) { - - // compare the strings - for (k = 0; k < len; ++k) { - if (p[k] != s2[k]) { - break; - } + // convert the line to uppercase + m = line->len; + if (!caseSensitive) { + if (m > txtSize) { + txt = (Unicode *)greallocn(txt, m, sizeof(Unicode)); + txtSize = m; } + for (k = 0; k < m; ++k) { + txt[k] = unicodeToUpper(line->text[k]); + } + } else { + txt = line->text; + } - // found it - if (k == len) { - switch (line->rot) { - case 0: - xMin1 = line->edge[j]; - xMax1 = line->edge[j + len]; - yMin1 = line->yMin; - yMax1 = line->yMax; - break; - case 1: - xMin1 = line->xMin; - xMax1 = line->xMax; - yMin1 = line->edge[j]; - yMax1 = line->edge[j + len]; - break; - case 2: - xMin1 = line->edge[j + len]; - xMax1 = line->edge[j]; - yMin1 = line->yMin; - yMax1 = line->yMax; - break; - case 3: - xMin1 = line->xMin; - xMax1 = line->xMax; - yMin1 = line->edge[j + len]; - yMax1 = line->edge[j]; - break; + // search each position in this line + j = backward ? m - len : 0; + p = txt + j; + while (backward ? j >= 0 : j <= m - len) { + if (!wholeWord || + ((j == 0 || !unicodeTypeWord(txt[j - 1])) && + (j + len == m || !unicodeTypeWord(txt[j + len])))) { + + // compare the strings + for (k = 0; k < len; ++k) { + if (p[k] != s2[k]) { + break; + } } - if (backward) { - if ((startAtTop || - yMin1 < yStart || (yMin1 == yStart && xMin1 < xStart)) && - (stopAtBottom || - yMin1 > yStop || (yMin1 == yStop && xMin1 > xStop))) { - if (!found || - yMin1 > yMin0 || (yMin1 == yMin0 && xMin1 > xMin0)) { - xMin0 = xMin1; - xMax0 = xMax1; - yMin0 = yMin1; - yMax0 = yMax1; - found = gTrue; - } + + // found it + if (k == len) { + switch (line->rot) { + case 0: + xMin1 = line->edge[j]; + xMax1 = line->edge[j + len]; + yMin1 = line->yMin; + yMax1 = line->yMax; + break; + case 1: + xMin1 = line->xMin; + xMax1 = line->xMax; + yMin1 = line->edge[j]; + yMax1 = line->edge[j + len]; + break; + case 2: + xMin1 = line->edge[j + len]; + xMax1 = line->edge[j]; + yMin1 = line->yMin; + yMax1 = line->yMax; + break; + case 3: + xMin1 = line->xMin; + xMax1 = line->xMax; + yMin1 = line->edge[j + len]; + yMax1 = line->edge[j]; + break; } - } else { - if ((startAtTop || - yMin1 > yStart || (yMin1 == yStart && xMin1 > xStart)) && - (stopAtBottom || - yMin1 < yStop || (yMin1 == yStop && xMin1 < xStop))) { - if (!found || - yMin1 < yMin0 || (yMin1 == yMin0 && xMin1 < xMin0)) { - xMin0 = xMin1; - xMax0 = xMax1; - yMin0 = yMin1; - yMax0 = yMax1; - found = gTrue; + if (backward) { + if ((startAtTop || + yMin1 < yStart || (yMin1 == yStart && xMin1 < xStart)) && + (stopAtBottom || + yMin1 > yStop || (yMin1 == yStop && xMin1 > xStop))) { + if (!found || + yMin1 > yMin0 || (yMin1 == yMin0 && xMin1 > xMin0)) { + xMin0 = xMin1; + xMax0 = xMax1; + yMin0 = yMin1; + yMax0 = yMax1; + found = gTrue; + } + } + } else { + if ((startAtTop || + yMin1 > yStart || (yMin1 == yStart && xMin1 > xStart)) && + (stopAtBottom || + yMin1 < yStop || (yMin1 == yStop && xMin1 < xStop))) { + if (!found || + yMin1 < yMin0 || (yMin1 == yMin0 && xMin1 < xMin0)) { + xMin0 = xMin1; + xMax0 = xMax1; + yMin0 = yMin1; + yMax0 = yMax1; + found = gTrue; + } } } } } - } - if (backward) { - --j; - --p; - } else { - ++j; - ++p; + if (backward) { + --j; + --p; + } else { + ++j; + ++p; + } } } } @@ -3339,32 +3871,27 @@ GBool TextPage::findText(Unicode *s, int len, GString *TextPage::getText(double xMin, double yMin, double xMax, double yMax) { - GString *s; UnicodeMap *uMap; - GBool isUnicode; - TextBlock *blk; - TextLine *line; - TextLineFrag *frags; - int nFrags, fragsSize; - TextLineFrag *frag; char space[8], eol[16]; int spaceLen, eolLen; - int lastRot; - double x, y, delta; - int col, idx0, idx1, i, j; - GBool multiLine, oneRot; - - s = new GString(); - - if (rawOrder) { - return s; - } + GList *chars2; + GString **out; + int *outLen; + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextChar *ch; + GBool primaryLR; + TextBlock *tree; + GList *columns; + GString *ret; + double xx, yy; + int rot, colIdx, parIdx, lineIdx, ph, y, i; // get the output encoding if (!(uMap = globalParams->getTextEncoding())) { - return s; + return NULL; } - isUnicode = uMap->isUnicode(); spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); eolLen = 0; // make gcc happy switch (globalParams->getTextEOL()) { @@ -3380,655 +3907,276 @@ GString *TextPage::getText(double xMin, double yMin, break; } - //~ writing mode (horiz/vert) - - // collect the line fragments that are in the rectangle - fragsSize = 256; - frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag)); - nFrags = 0; - lastRot = -1; - oneRot = gTrue; - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - if (xMin < blk->xMax && blk->xMin < xMax && - yMin < blk->yMax && blk->yMin < yMax) { - for (line = blk->lines; line; line = line->next) { - if (xMin < line->xMax && line->xMin < xMax && - yMin < line->yMax && line->yMin < yMax) { - idx0 = idx1 = -1; - switch (line->rot) { - case 0: - y = 0.5 * (line->yMin + line->yMax); - if (yMin < y && y < yMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) { - idx1 = j; - break; - } - --j; - } - } - break; - case 1: - x = 0.5 * (line->xMin + line->xMax); - if (xMin < x && x < xMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) { - idx1 = j; - break; - } - --j; - } - } - break; - case 2: - y = 0.5 * (line->yMin + line->yMax); - if (yMin < y && y < yMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) { - idx1 = j; - break; - } - --j; - } - } - break; - case 3: - x = 0.5 * (line->xMin + line->xMax); - if (xMin < x && x < xMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) { - idx1 = j; - break; - } - --j; - } - } - break; - } - if (idx0 >= 0 && idx1 >= 0) { - if (nFrags == fragsSize) { - fragsSize *= 2; - frags = (TextLineFrag *) - greallocn(frags, fragsSize, sizeof(TextLineFrag)); - } - frags[nFrags].init(line, idx0, idx1 - idx0 + 1); - ++nFrags; - if (lastRot >= 0 && line->rot != lastRot) { - oneRot = gFalse; - } - lastRot = line->rot; - } - } - } + // get all chars in the rectangle + // (i.e., all chars whose center lies inside the rectangle) + chars2 = new GList(); + for (i = 0; i < chars->getLength(); ++i) { + ch = (TextChar *)chars->get(i); + xx = 0.5 * (ch->xMin + ch->xMax); + yy = 0.5 * (ch->yMin + ch->yMax); + if (xx > xMin && xx < xMax && yy > yMin && yy < yMax) { + chars2->append(ch); } } +#if 0 //~debug + dumpChars(chars2); +#endif - // sort the fragments and generate the string - if (nFrags > 0) { - - for (i = 0; i < nFrags; ++i) { - frags[i].computeCoords(oneRot); - } - assignColumns(frags, nFrags, oneRot); - - // if all lines in the region have the same rotation, use it; - // otherwise, use the page's primary rotation - if (oneRot) { - qsort(frags, nFrags, sizeof(TextLineFrag), - &TextLineFrag::cmpYXLineRot); - } else { - qsort(frags, nFrags, sizeof(TextLineFrag), - &TextLineFrag::cmpYXPrimaryRot); - } - i = 0; - while (i < nFrags) { - delta = maxIntraLineDelta * frags[i].line->words->fontSize; - for (j = i+1; - j < nFrags && fabs(frags[j].base - frags[i].base) < delta; - ++j) ; - qsort(frags + i, j - i, sizeof(TextLineFrag), - oneRot ? &TextLineFrag::cmpXYColumnLineRot - : &TextLineFrag::cmpXYColumnPrimaryRot); - i = j; - } - - col = 0; - multiLine = gFalse; - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - - // insert a return - if (frag->col < col || - (i > 0 && fabs(frag->base - frags[i-1].base) > - maxIntraLineDelta * frags[i-1].line->words->fontSize)) { - s->append(eol, eolLen); - col = 0; - multiLine = gTrue; + rot = rotateChars(chars2); + primaryLR = checkPrimaryLR(chars2); + tree = splitChars(chars2); + if (!tree) { + unrotateChars(chars2, rot); + delete chars2; + return new GString(); + } +#if 0 //~debug + dumpTree(tree); +#endif + columns = buildColumns(tree); + delete tree; + ph = assignPhysLayoutPositions(columns); +#if 0 //~debug + dumpColumns(columns); +#endif + unrotateChars(chars2, rot); + delete chars2; + + out = (GString **)gmallocn(ph, sizeof(GString *)); + outLen = (int *)gmallocn(ph, sizeof(int)); + for (i = 0; i < ph; ++i) { + out[i] = NULL; + outLen[i] = 0; + } + + columns->sort(&TextColumn::cmpPX); + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + y = col->py; + for (parIdx = 0; + parIdx < col->paragraphs->getLength() && y < ph; + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; + lineIdx < par->lines->getLength() && y < ph; + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + if (!out[y]) { + out[y] = new GString(); + } + while (outLen[y] < col->px + line->px) { + out[y]->append(space, spaceLen); + ++outLen[y]; + } + encodeFragment(line->text, line->len, uMap, primaryLR, out[y]); + outLen[y] += line->pw; + ++y; } - - // column alignment - for (; col < frag->col; ++col) { - s->append(space, spaceLen); + if (parIdx + 1 < col->paragraphs->getLength()) { + ++y; } - - // get the fragment text - col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s); } + } - if (multiLine) { - s->append(eol, eolLen); + ret = new GString(); + for (i = 0; i < ph; ++i) { + if (out[i]) { + ret->append(out[i]); + delete out[i]; + } + if (ph > 1) { + ret->append(eol, eolLen); } } - gfree(frags); + gfree(out); + gfree(outLen); + deleteGList(columns, TextColumn); uMap->decRefCnt(); - return s; + return ret; } GBool TextPage::findCharRange(int pos, int length, double *xMin, double *yMin, double *xMax, double *yMax) { - TextBlock *blk; - TextLine *line; - TextWord *word; - double xMin0, xMax0, yMin0, yMax0; - double xMin1, xMax1, yMin1, yMax1; + TextChar *ch; + double xMin2, yMin2, xMax2, yMax2; GBool first; - int i, j0, j1; - - if (rawOrder) { - return gFalse; - } + int i; //~ this doesn't correctly handle ranges split across multiple lines //~ (the highlighted region is the bounding box of all the parts of //~ the range) + + xMin2 = yMin2 = xMax2 = yMax2 = 0; first = gTrue; - xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy - xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - if (pos < word->charPos[word->len] && - pos + length > word->charPos[0]) { - for (j0 = 0; - j0 < word->len && pos >= word->charPos[j0 + 1]; - ++j0) ; - for (j1 = word->len - 1; - j1 > j0 && pos + length <= word->charPos[j1]; - --j1) ; - switch (line->rot) { - case 0: - xMin1 = word->edge[j0]; - xMax1 = word->edge[j1 + 1]; - yMin1 = word->yMin; - yMax1 = word->yMax; - break; - case 1: - xMin1 = word->xMin; - xMax1 = word->xMax; - yMin1 = word->edge[j0]; - yMax1 = word->edge[j1 + 1]; - break; - case 2: - xMin1 = word->edge[j1 + 1]; - xMax1 = word->edge[j0]; - yMin1 = word->yMin; - yMax1 = word->yMax; - break; - case 3: - xMin1 = word->xMin; - xMax1 = word->xMax; - yMin1 = word->edge[j1 + 1]; - yMax1 = word->edge[j0]; - break; - } - if (first || xMin1 < xMin0) { - xMin0 = xMin1; - } - if (first || xMax1 > xMax0) { - xMax0 = xMax1; - } - if (first || yMin1 < yMin0) { - yMin0 = yMin1; - } - if (first || yMax1 > yMax0) { - yMax0 = yMax1; - } - first = gFalse; - } + for (i = 0; i < chars->getLength(); ++i) { + ch = (TextChar *)chars->get(i); + if (ch->charPos >= pos && ch->charPos < pos + length) { + if (first || ch->xMin < xMin2) { + xMin2 = ch->xMin; + } + if (first || ch->yMin < yMin2) { + yMin2 = ch->yMin; + } + if (first || ch->xMax > xMax2) { + xMax2 = ch->xMax; } + if (first || ch->yMax > yMax2) { + yMax2 = ch->yMax; + } + first = gFalse; } } - if (!first) { - *xMin = xMin0; - *xMax = xMax0; - *yMin = yMin0; - *yMax = yMax0; - return gTrue; + if (first) { + return gFalse; } - return gFalse; + *xMin = xMin2; + *yMin = yMin2; + *xMax = xMax2; + *yMax = yMax2; + return gTrue; } -void TextPage::dump(void *outputStream, TextOutputFunc outputFunc, - GBool physLayout) { - UnicodeMap *uMap; - TextFlow *flow; - TextBlock *blk; +TextWordList *TextPage::makeWordList() { + TextBlock *tree; + GList *columns; + TextColumn *col; + TextParagraph *par; TextLine *line; - TextLineFrag *frags; TextWord *word; - int nFrags, fragsSize; - TextLineFrag *frag; - char space[8], eol[16], eop[8]; - int spaceLen, eolLen, eopLen; - GBool pageBreaks; - GString *s; - double delta; - int col, i, j, d, n; + GList *words; + int rot, colIdx, parIdx, lineIdx, wordIdx; - // get the output encoding - if (!(uMap = globalParams->getTextEncoding())) { - return; + rot = rotateChars(chars); + tree = splitChars(chars); + if (!tree) { + // no text + unrotateChars(chars, rot); + return new TextWordList(new GList()); } - spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); - eolLen = 0; // make gcc happy - switch (globalParams->getTextEOL()) { - case eolUnix: - eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); - break; - case eolDOS: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen); - break; - case eolMac: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - break; + columns = buildColumns(tree); + delete tree; + unrotateChars(chars, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); } - eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop)); - pageBreaks = globalParams->getTextPageBreaks(); - //~ writing mode (horiz/vert) - - // output the page in raw (content stream) order - if (rawOrder) { - - for (word = rawWords; word; word = word->next) { - s = new GString(); - dumpFragment(word->text, word->len, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - if (word->next && - fabs(word->next->base - word->base) < - maxIntraLineDelta * word->fontSize && - word->next->xMin > - word->xMax - minDupBreakOverlap * word->fontSize) { - if (word->next->xMin > word->xMax + minWordSpacing * word->fontSize) { - (*outputFunc)(outputStream, space, spaceLen); + words = new GList(); + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + words->append(word->copy()); } - } else { - (*outputFunc)(outputStream, eol, eolLen); } } + } - // output the page, maintaining the original physical layout - } else if (physLayout) { - - // collect the line fragments for the page and sort them - fragsSize = 256; - frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag)); - nFrags = 0; - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - for (line = blk->lines; line; line = line->next) { - if (nFrags == fragsSize) { - fragsSize *= 2; - frags = (TextLineFrag *)greallocn(frags, - fragsSize, sizeof(TextLineFrag)); - } - frags[nFrags].init(line, 0, line->len); - frags[nFrags].computeCoords(gTrue); - ++nFrags; - } - } - qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpYXPrimaryRot); - i = 0; - while (i < nFrags) { - delta = maxIntraLineDelta * frags[i].line->words->fontSize; - for (j = i+1; - j < nFrags && fabs(frags[j].base - frags[i].base) < delta; - ++j) ; - qsort(frags + i, j - i, sizeof(TextLineFrag), - &TextLineFrag::cmpXYColumnPrimaryRot); - i = j; - } - -#if 0 // for debugging - printf("*** line fragments ***\n"); - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - printf("frag: x=%.2f..%.2f y=%.2f..%.2f base=%.2f '", - frag->xMin, frag->xMax, frag->yMin, frag->yMax, frag->base); - for (n = 0; n < frag->len; ++n) { - fputc(frag->line->text[frag->start + n] & 0xff, stdout); - } - printf("'\n"); - } - printf("\n"); -#endif + switch (control.mode) { + case textOutReadingOrder: + // already in reading order + break; + case textOutPhysLayout: + case textOutTableLayout: + case textOutLinePrinter: + words->sort(&TextWord::cmpYX); + break; + case textOutRawOrder: + words->sort(&TextWord::cmpCharPos); + break; + } - // generate output - col = 0; - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; + // this has to be done after sorting with cmpYX + unrotateColumns(columns, rot); + unrotateWords(words, rot); - // column alignment - for (; col < frag->col; ++col) { - (*outputFunc)(outputStream, space, spaceLen); - } + deleteGList(columns, TextColumn); - // print the line - s = new GString(); - col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - - // print one or more returns if necessary - if (i == nFrags - 1 || - frags[i+1].col < col || - fabs(frags[i+1].base - frag->base) > - maxIntraLineDelta * frag->line->words->fontSize) { - if (i < nFrags - 1) { - d = (int)((frags[i+1].base - frag->base) / - frag->line->words->fontSize); - if (d < 1) { - d = 1; - } else if (d > 5) { - d = 5; - } - } else { - d = 1; - } - for (; d > 0; --d) { - (*outputFunc)(outputStream, eol, eolLen); - } - col = 0; - } - } + return new TextWordList(words); +} - gfree(frags); +//------------------------------------------------------------------------ +// TextPage: debug +//------------------------------------------------------------------------ - // output the page, "undoing" the layout - } else { - for (flow = flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - n = line->len; - if (line->hyphenated && (line->next || blk->next)) { - --n; - } - s = new GString(); - dumpFragment(line->text, n, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - if (!line->hyphenated) { - if (line->next) { - (*outputFunc)(outputStream, space, spaceLen); - } else if (blk->next) { - //~ this is a bit of a kludge - we should really do a more - //~ intelligent determination of paragraphs - if (blk->next->lines->words->fontSize == - blk->lines->words->fontSize) { - (*outputFunc)(outputStream, space, spaceLen); - } else { - (*outputFunc)(outputStream, eol, eolLen); - } - } - } - } - } - (*outputFunc)(outputStream, eol, eolLen); - (*outputFunc)(outputStream, eol, eolLen); - } - } +#if 0 //~debug - // end of page - if (pageBreaks) { - (*outputFunc)(outputStream, eop, eopLen); - } +void TextPage::dumpChars(GList *charsA) { + TextChar *ch; + int i; - uMap->decRefCnt(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + printf("char: U+%04x '%c' xMin=%g yMin=%g xMax=%g yMax=%g fontSize=%g rot=%d\n", + ch->c, ch->c & 0xff, ch->xMin, ch->yMin, ch->xMax, ch->yMax, + ch->fontSize, ch->rot); + } } -void TextPage::assignColumns(TextLineFrag *frags, int nFrags, GBool oneRot) { - TextLineFrag *frag0, *frag1; - int rot, col1, col2, i, j, k; - - // all text in the region has the same rotation -- recompute the - // column numbers based only on the text in the region - if (oneRot) { - qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpXYLineRot); - rot = frags[0].line->rot; - for (i = 0; i < nFrags; ++i) { - frag0 = &frags[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - frag1 = &frags[j]; - col2 = 0; // make gcc happy - switch (rot) { - case 0: - if (frag0->xMin >= frag1->xMax) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->xMin >= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 1: - if (frag0->yMin >= frag1->yMax) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->yMin >= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 2: - if (frag0->xMax <= frag1->xMin) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->xMax <= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 3: - if (frag0->yMax <= frag1->yMin) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->yMax <= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - } - if (col2 > col1) { - col1 = col2; - } - } - frag0->col = col1; - } +void TextPage::dumpTree(TextBlock *tree, int indent) { + TextChar *ch; + int i; - // the region includes text at different rotations -- use the - // globally assigned column numbers, offset by the minimum column - // number (i.e., shift everything over to column 0) - } else { - col1 = frags[0].col; - for (i = 1; i < nFrags; ++i) { - if (frags[i].col < col1) { - col1 = frags[i].col; - } + printf("%*sblock: type=%s tag=%s small=%d rot=%d xMin=%g yMin=%g xMax=%g yMax=%g\n", + indent, "", + tree->type == blkLeaf ? "leaf" : + tree->type == blkHorizSplit ? "horiz" : "vert", + tree->tag == blkTagMulticolumn ? "multicolumn" : + tree->tag == blkTagColumn ? "column" : "line", + tree->smallSplit, + tree->rot, tree->xMin, tree->yMin, tree->xMax, tree->yMax); + if (tree->type == blkLeaf) { + for (i = 0; i < tree->children->getLength(); ++i) { + ch = (TextChar *)tree->children->get(i); + printf("%*schar: '%c' xMin=%g yMin=%g xMax=%g yMax=%g font=%d.%d\n", + indent + 2, "", ch->c & 0xff, + ch->xMin, ch->yMin, ch->xMax, ch->yMax, + ch->font->fontID.num, ch->font->fontID.gen); } - for (i = 0; i < nFrags; ++i) { - frags[i].col -= col1; + } else { + for (i = 0; i < tree->children->getLength(); ++i) { + dumpTree((TextBlock *)tree->children->get(i), indent + 2); } } } -int TextPage::dumpFragment(Unicode *text, int len, UnicodeMap *uMap, - GString *s) { - char lre[8], rle[8], popdf[8], buf[8]; - int lreLen, rleLen, popdfLen, n; - int nCols, i, j, k; - - nCols = 0; - - if (uMap->isUnicode()) { - - lreLen = uMap->mapUnicode(0x202a, lre, sizeof(lre)); - rleLen = uMap->mapUnicode(0x202b, rle, sizeof(rle)); - popdfLen = uMap->mapUnicode(0x202c, popdf, sizeof(popdf)); - - if (primaryLR) { - - i = 0; - while (i < len) { - // output a left-to-right section - for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ; - for (k = i; k < j; ++k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - i = j; - // output a right-to-left section - for (j = i; - j < len && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); - ++j) ; - if (j > i) { - s->append(rle, rleLen); - for (k = j - 1; k >= i; --k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - s->append(popdf, popdfLen); - i = j; - } - } - - } else { - - // Note: This code treats numeric characters (European and - // Arabic/Indic) as left-to-right, which isn't strictly correct - // (incurs extra LRE/POPDF pairs), but does produce correct - // visual formatting. - s->append(rle, rleLen); - i = len - 1; - while (i >= 0) { - // output a right-to-left section - for (j = i; - j >= 0 && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); - --j) ; - for (k = i; k > j; --k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - i = j; - // output a left-to-right section - for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ; - if (j < i) { - s->append(lre, lreLen); - for (k = j + 1; k <= i; ++k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - s->append(popdf, popdfLen); - i = j; +void TextPage::dumpColumns(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + int colIdx, parIdx, lineIdx, i; + + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + printf("column: xMin=%g yMin=%g xMax=%g yMax=%g px=%d py=%d pw=%d ph=%d\n", + col->xMin, col->yMin, col->xMax, col->yMax, + col->px, col->py, col->pw, col->ph); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + printf(" paragraph:\n"); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + printf(" line: xMin=%g yMin=%g xMax=%g yMax=%g px=%d pw=%d rot=%d\n", + line->xMin, line->yMin, line->xMax, line->yMax, + line->px, line->pw, line->rot); + printf(" "); + for (i = 0; i < line->len; ++i) { + printf("%c", line->text[i] & 0xff); } + printf("\n"); } - s->append(popdf, popdfLen); - - } - - } else { - for (i = 0; i < len; ++i) { - n = uMap->mapUnicode(text[i], buf, sizeof(buf)); - s->append(buf, n); - nCols += n; } } - - return nCols; } -#if TEXTOUT_WORD_LIST -TextWordList *TextPage::makeWordList(GBool physLayout) { - return new TextWordList(this, physLayout); -} -#endif +#endif //~debug //------------------------------------------------------------------------ // TextOutputDev @@ -4038,14 +4186,10 @@ static void outputToFile(void *stream, const char *text, int len) { fwrite(text, 1, len, (FILE *)stream); } -TextOutputDev::TextOutputDev(char *fileName, GBool physLayoutA, - double fixedPitchA, GBool rawOrderA, +TextOutputDev::TextOutputDev(char *fileName, TextOutputControl *controlA, GBool append) { text = NULL; - physLayout = physLayoutA; - fixedPitch = physLayout ? fixedPitchA : 0; - rawOrder = rawOrderA; - doHTML = gFalse; + control = *controlA; ok = gTrue; // open file @@ -4070,28 +4214,21 @@ TextOutputDev::TextOutputDev(char *fileName, GBool physLayoutA, } // set up text object - text = new TextPage(rawOrderA); + text = new TextPage(&control); } TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream, - GBool physLayoutA, double fixedPitchA, - GBool rawOrderA) { + TextOutputControl *controlA) { outputFunc = func; outputStream = stream; needClose = gFalse; - physLayout = physLayoutA; - fixedPitch = physLayout ? fixedPitchA : 0; - rawOrder = rawOrderA; - doHTML = gFalse; - text = new TextPage(rawOrderA); + control = *controlA; + text = new TextPage(&control); ok = gTrue; } TextOutputDev::~TextOutputDev() { if (needClose) { -#ifdef MACOS - ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle); -#endif fclose((FILE *)outputStream); } if (text) { @@ -4104,10 +4241,8 @@ void TextOutputDev::startPage(int pageNum, GfxState *state) { } void TextOutputDev::endPage() { - text->endPage(); - text->coalesce(physLayout, fixedPitch, doHTML); if (outputStream) { - text->dump(outputStream, outputFunc, physLayout); + text->write(outputStream, outputFunc); } } @@ -4129,7 +4264,7 @@ void TextOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode c, int nBytes, Unicode *u, int uLen) { - text->addChar(state, x - originX, y - originY, dx, dy, c, nBytes, u, uLen); + text->addChar(state, x, y, dx, dy, c, nBytes, u, uLen); } void TextOutputDev::incCharCount(int nChars) { @@ -4149,7 +4284,7 @@ void TextOutputDev::stroke(GfxState *state) { GfxSubpath *subpath; double x[2], y[2]; - if (!doHTML) { + if (!control.html) { return; } path = state->getPath(); @@ -4176,7 +4311,7 @@ void TextOutputDev::fill(GfxState *state) { double rx0, ry0, rx1, ry1, t; int i; - if (!doHTML) { + if (!control.html) { return; } path = state->getPath(); @@ -4238,7 +4373,7 @@ void TextOutputDev::fill(GfxState *state) { } void TextOutputDev::eoFill(GfxState *state) { - if (!doHTML) { + if (!control.html) { return; } fill(state); @@ -4248,7 +4383,7 @@ void TextOutputDev::processLink(Link *link) { double x1, y1, x2, y2; int xMin, yMin, xMax, yMax, x, y; - if (!doHTML) { + if (!control.html) { return; } link->getRect(&x1, &y1, &x2, &y2); @@ -4315,16 +4450,14 @@ GBool TextOutputDev::findCharRange(int pos, int length, return text->findCharRange(pos, length, xMin, yMin, xMax, yMax); } -#if TEXTOUT_WORD_LIST TextWordList *TextOutputDev::makeWordList() { - return text->makeWordList(physLayout); + return text->makeWordList(); } -#endif TextPage *TextOutputDev::takeText() { TextPage *ret; ret = text; - text = new TextPage(rawOrder); + text = new TextPage(&control); return ret; } diff --git a/xpdf/TextOutputDev.h b/xpdf/TextOutputDev.h index e3bb26c..4399029 100644 --- a/xpdf/TextOutputDev.h +++ b/xpdf/TextOutputDev.h @@ -2,7 +2,7 @@ // // TextOutputDev.h // -// Copyright 1997-2003 Glyph & Cog, LLC +// Copyright 1997-2012 Glyph & Cog, LLC // //======================================================================== @@ -20,20 +20,12 @@ #include "GfxFont.h" #include "OutputDev.h" -class GString; class GList; -class GfxFont; -class GfxState; class UnicodeMap; -class Link; -class TextWord; -class TextPool; -class TextLine; -class TextLineFrag; class TextBlock; -class TextFlow; -class TextWordList; +class TextChar; +class TextLink; class TextPage; //------------------------------------------------------------------------ @@ -41,6 +33,37 @@ class TextPage; typedef void (*TextOutputFunc)(void *stream, const char *text, int len); //------------------------------------------------------------------------ +// TextOutputControl +//------------------------------------------------------------------------ + +enum TextOutputMode { + textOutReadingOrder, // format into reading order + textOutPhysLayout, // maintain original physical layout + textOutTableLayout, // similar to PhysLayout, but optimized + // for tables + textOutLinePrinter, // strict fixed-pitch/height layout + textOutRawOrder // keep text in content stream order +}; + +class TextOutputControl { +public: + + TextOutputControl(); + ~TextOutputControl() {} + + TextOutputMode mode; // formatting mode + double fixedPitch; // if this is non-zero, assume fixed-pitch + // characters with this width + // (only relevant for PhysLayout, Table, + // and LinePrinter modes) + double fixedLineSpacing; // fixed line spacing (only relevant for + // LinePrinter mode) + GBool html; // enable extra processing for HTML + GBool clipText; // separate clipped text and add it back + // in after forming columns +}; + +//------------------------------------------------------------------------ // TextFontInfo //------------------------------------------------------------------------ @@ -52,7 +75,6 @@ public: GBool matches(GfxState *state); -#if TEXTOUT_WORD_LIST // Get the font name (which may be NULL). GString *getFontName() { return fontName; } @@ -62,18 +84,21 @@ public: GBool isSymbolic() { return flags & fontSymbolic; } GBool isItalic() { return flags & fontItalic; } GBool isBold() { return flags & fontBold; } -#endif + + // Get the width of the 'm' character, if available. + double getMWidth() { return mWidth; } private: - GfxFont *gfxFont; -#if TEXTOUT_WORD_LIST + Ref fontID; GString *fontName; int flags; -#endif + double mWidth; + double ascent, descent; - friend class TextWord; + friend class TextLine; friend class TextPage; + friend class TextWord; }; //------------------------------------------------------------------------ @@ -83,44 +108,21 @@ private: class TextWord { public: - // Constructor. - TextWord(GfxState *state, int rotA, double x0, double y0, - TextFontInfo *fontA, double fontSize); - - // Destructor. + TextWord(GList *chars, int start, int lenA, + int rotA, GBool spaceAfterA); ~TextWord(); - - // Add a character to the word. - void addChar(GfxState *state, double x, double y, - double dx, double dy, int charPosA, int charLen, - Unicode u); - - // Merge <word> onto the end of <this>. - void merge(TextWord *word); - - // Compares <this> to <word>, returning -1 (<), 0 (=), or +1 (>), - // based on a primary-axis comparison, e.g., x ordering if rot=0. - int primaryCmp(TextWord *word); - - // Return the distance along the primary axis between <this> and - // <word>. - double primaryDelta(TextWord *word); - - static int cmpYX(const void *p1, const void *p2); + TextWord *copy() { return new TextWord(this); } // Get the TextFontInfo object associated with this word. TextFontInfo *getFontInfo() { return font; } - // Get the next TextWord on the linked list. - TextWord *getNext() { return next; } - -#if TEXTOUT_WORD_LIST int getLength() { return len; } Unicode getChar(int idx) { return text[idx]; } GString *getText(); GString *getFontName() { return font->fontName; } void getColor(double *r, double *g, double *b) { *r = colorR; *g = colorG; *b = colorB; } + GBool isInvisible() { return invisible; } void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA) { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; } void getCharBBox(int charIdx, double *xMinA, double *yMinA, @@ -130,76 +132,43 @@ public: int getCharPos() { return charPos[0]; } int getCharLen() { return charPos[len] - charPos[0]; } GBool getSpaceAfter() { return spaceAfter; } -#endif - + double getBaseline(); GBool isUnderlined() { return underlined; } - Link *getLink() { return link; } + GString *getLinkURI(); private: + TextWord(TextWord *word); + void appendChar(TextChar *ch); + static int cmpYX(const void *p1, const void *p2); + static int cmpCharPos(const void *p1, const void *p2); + int rot; // rotation, multiple of 90 degrees // (0, 1, 2, or 3) double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double base; // baseline x or y coordinate Unicode *text; // the text - double *edge; // "near" edge x or y coord of each char - // (plus one extra entry for the last char) int *charPos; // character position (within content stream) // of each char (plus one extra entry for // the last char) - int len; // length of text/edge/charPos arrays - int size; // size of text/edge/charPos arrays + double *edge; // "near" edge x or y coord of each char + // (plus one extra entry for the last char) + int len; // number of characters TextFontInfo *font; // font information double fontSize; // font size GBool spaceAfter; // set if there is a space between this // word and the next word on the line - TextWord *next; // next word in line -#if TEXTOUT_WORD_LIST + GBool underlined; + TextLink *link; + double colorR, // word color colorG, colorB; -#endif - - GBool underlined; - Link *link; - - friend class TextPool; - friend class TextLine; - friend class TextBlock; - friend class TextFlow; - friend class TextWordList; - friend class TextPage; -}; - -//------------------------------------------------------------------------ -// TextPool -//------------------------------------------------------------------------ - -class TextPool { -public: - - TextPool(); - ~TextPool(); - - TextWord *getPool(int baseIdx) { return pool[baseIdx - minBaseIdx]; } - void setPool(int baseIdx, TextWord *p) { pool[baseIdx - minBaseIdx] = p; } - - int getBaseIdx(double base); - - void addWord(TextWord *word); - -private: - - int minBaseIdx; // min baseline bucket index - int maxBaseIdx; // max baseline bucket index - TextWord **pool; // array of linked lists, one for each - // baseline value (multiple of 4 pts) - TextWord *cursor; // pointer to last-accessed word - int cursorBaseIdx; // baseline bucket index of last-accessed word + GBool invisible; // set for invisible text (render mode 3) friend class TextBlock; + friend class TextLine; friend class TextPage; }; @@ -210,168 +179,92 @@ private: class TextLine { public: - TextLine(TextBlock *blkA, int rotA, double baseA); + TextLine(GList *wordsA, double xMinA, double yMinA, + double xMaxA, double yMaxA, double fontSizeA); ~TextLine(); - void addWord(TextWord *word); - - // Return the distance along the primary axis between <this> and - // <line>. - double primaryDelta(TextLine *line); - - // Compares <this> to <line>, returning -1 (<), 0 (=), or +1 (>), - // based on a primary-axis comparison, e.g., x ordering if rot=0. - int primaryCmp(TextLine *line); - - // Compares <this> to <line>, returning -1 (<), 0 (=), or +1 (>), - // based on a secondary-axis comparison of the baselines, e.g., y - // ordering if rot=0. - int secondaryCmp(TextLine *line); - - int cmpYX(TextLine *line); - - static int cmpXY(const void *p1, const void *p2); - - void coalesce(UnicodeMap *uMap); - - // Get the head of the linked list of TextWords. - TextWord *getWords() { return words; } - - // Get the next TextLine on the linked list. - TextLine *getNext() { return next; } - - // Returns true if the last char of the line is a hyphen. - GBool isHyphenated() { return hyphenated; } + double getXMin() { return xMin; } + double getYMin() { return yMin; } + double getBaseline(); + int getRotation() { return rot; } + GList *getWords() { return words; } private: - TextBlock *blk; // parent block - int rot; // text rotation + GList *words; // [TextWord] + int rot; // rotation, multiple of 90 degrees + // (0, 1, 2, or 3) double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double base; // baseline x or y coordinate - TextWord *words; // words in this line - TextWord *lastWord; // last word in this line + double fontSize; // main (max) font size for this line Unicode *text; // Unicode text of the line, including // spaces between words double *edge; // "near" edge x or y coord of each char // (plus one extra entry for the last char) - int *col; // starting column number of each Unicode char int len; // number of Unicode chars - int convertedLen; // total number of converted characters GBool hyphenated; // set if last char is a hyphen - TextLine *next; // next line in block + int px; // x offset (in characters, relative to + // containing column) in physical layout mode + int pw; // line width (in characters) in physical + // layout mode - friend class TextLineFrag; - friend class TextBlock; - friend class TextFlow; - friend class TextWordList; friend class TextPage; + friend class TextParagraph; }; //------------------------------------------------------------------------ -// TextBlock +// TextParagraph //------------------------------------------------------------------------ -class TextBlock { +class TextParagraph { public: - TextBlock(TextPage *pageA, int rotA); - ~TextBlock(); - - void addWord(TextWord *word); - - void coalesce(UnicodeMap *uMap, double fixedPitch); - - // Update this block's priMin and priMax values, looking at <blk>. - void updatePriMinMax(TextBlock *blk); - - static int cmpXYPrimaryRot(const void *p1, const void *p2); - - static int cmpYXPrimaryRot(const void *p1, const void *p2); - - int primaryCmp(TextBlock *blk); + TextParagraph(GList *linesA); + ~TextParagraph(); - double secondaryDelta(TextBlock *blk); - - // Returns true if <this> is below <blk>, relative to the page's - // primary rotation. - GBool isBelow(TextBlock *blk); - - // Get the head of the linked list of TextLines. - TextLine *getLines() { return lines; } - - // Get the next TextBlock on the linked list. - TextBlock *getNext() { return next; } + // Get the list of TextLine objects. + GList *getLines() { return lines; } private: - TextPage *page; // the parent page - int rot; // text rotation + GList *lines; // [TextLine] double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double priMin, priMax; // whitespace bounding box along primary axis - - TextPool *pool; // pool of words (used only until lines - // are built) - TextLine *lines; // linked list of lines - TextLine *curLine; // most recently added line - int nLines; // number of lines - int charCount; // number of characters in the block - int col; // starting column - int nColumns; // number of columns in the block - TextBlock *next; - TextBlock *stackNext; - - friend class TextLine; - friend class TextLineFrag; - friend class TextFlow; - friend class TextWordList; friend class TextPage; }; //------------------------------------------------------------------------ -// TextFlow +// TextColumn //------------------------------------------------------------------------ -class TextFlow { +class TextColumn { public: - TextFlow(TextPage *pageA, TextBlock *blk); - ~TextFlow(); - - // Add a block to the end of this flow. - void addBlock(TextBlock *blk); - - // Returns true if <blk> fits below <prevBlk> in the flow, i.e., (1) - // it uses a font no larger than the last block added to the flow, - // and (2) it fits within the flow's [priMin, priMax] along the - // primary axis. - GBool blockFits(TextBlock *blk, TextBlock *prevBlk); + TextColumn(GList *paragraphsA, double xMinA, double yMinA, + double xMaxA, double yMaxA); + ~TextColumn(); - // Get the head of the linked list of TextBlocks. - TextBlock *getBlocks() { return blocks; } - - // Get the next TextFlow on the linked list. - TextFlow *getNext() { return next; } + // Get the list of TextParagraph objects. + GList *getParagraphs() { return paragraphs; } private: - TextPage *page; // the parent page + static int cmpX(const void *p1, const void *p2); + static int cmpY(const void *p1, const void *p2); + static int cmpPX(const void *p1, const void *p2); + + GList *paragraphs; // [TextParagraph] double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double priMin, priMax; // whitespace bounding box along primary axis - TextBlock *blocks; // blocks in flow - TextBlock *lastBlk; // last block in this flow - TextFlow *next; + int px, py; // x, y position (in characters) in physical + // layout mode + int pw, ph; // column width, height (in characters) in + // physical layout mode - friend class TextWordList; friend class TextPage; }; -#if TEXTOUT_WORD_LIST - //------------------------------------------------------------------------ // TextWordList //------------------------------------------------------------------------ @@ -379,11 +272,7 @@ private: class TextWordList { public: - // Build a flat word list, in content stream order (if - // text->rawOrder is true), physical layout order (if <physLayout> - // is true and text->rawOrder is false), or reading order (if both - // flags are false). - TextWordList(TextPage *text, GBool physLayout); + TextWordList(GList *wordsA); ~TextWordList(); @@ -398,8 +287,6 @@ private: GList *words; // [TextWord] }; -#endif // TEXTOUT_WORD_LIST - //------------------------------------------------------------------------ // TextPage //------------------------------------------------------------------------ @@ -407,52 +294,11 @@ private: class TextPage { public: - // Constructor. - TextPage(GBool rawOrderA); - - // Destructor. + TextPage(TextOutputControl *controlA); ~TextPage(); - // Start a new page. - void startPage(GfxState *state); - - // End the current page. - void endPage(); - - // Update the current font. - void updateFont(GfxState *state); - - // Begin a new word. - void beginWord(GfxState *state, double x0, double y0); - - // Add a character to the current word. - void addChar(GfxState *state, double x, double y, - double dx, double dy, - CharCode c, int nBytes, Unicode *u, int uLen); - - // Add <nChars> invisible characters. - void incCharCount(int nChars); - - // Begin/end an "ActualText" span, where the char indexes are - // supplied by a marked content operator rather than the text - // drawing operators. - void beginActualText(GfxState *state, Unicode *u, int uLen); - void endActualText(GfxState *state); - - // End the current word, sorting it into the list of words. - void endWord(); - - // Add a word, sorting it into the list of words. - void addWord(TextWord *word); - - // Add a (potential) underline. - void addUnderline(double x0, double y0, double x1, double y1); - - // Add a hyperlink. - void addLink(int xMin, int yMin, int xMax, int yMax, Link *link); - - // Coalesce strings that look like parts of the same line. - void coalesce(GBool physLayout, double fixedPitch, GBool doHTML); + // Write contents of page to a stream. + void write(void *outputStream, TextOutputFunc outputFunc); // Find a string. If <startAtTop> is true, starts looking at the // top of the page; else if <startAtLast> is true, starts looking @@ -480,39 +326,106 @@ public: double *xMin, double *yMin, double *xMax, double *yMax); - // Dump contents of page to a file. - void dump(void *outputStream, TextOutputFunc outputFunc, - GBool physLayout); + // Create and return a list of TextColumn objects. + GList *makeColumns(); - // Get the head of the linked list of TextFlows. - TextFlow *getFlows() { return flows; } + // Get the list of all TextFontInfo objects used on this page. + GList *getFonts() { return fonts; } -#if TEXTOUT_WORD_LIST - // Build a flat word list, in content stream order (if - // this->rawOrder is true), physical layout order (if <physLayout> - // is true and this->rawOrder is false), or reading order (if both - // flags are false). - TextWordList *makeWordList(GBool physLayout); -#endif + // Build a flat word list, in the specified ordering. + TextWordList *makeWordList(); private: + void startPage(GfxState *state); void clear(); - void assignColumns(TextLineFrag *frags, int nFrags, int rot); - int dumpFragment(Unicode *text, int len, UnicodeMap *uMap, GString *s); + void updateFont(GfxState *state); + void addChar(GfxState *state, double x, double y, + double dx, double dy, + CharCode c, int nBytes, Unicode *u, int uLen); + void incCharCount(int nChars); + void beginActualText(GfxState *state, Unicode *u, int uLen); + void endActualText(GfxState *state); + void addUnderline(double x0, double y0, double x1, double y1); + void addLink(double xMin, double yMin, double xMax, double yMax, + Link *link); + + // output + void writeReadingOrder(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void writePhysLayout(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void writeLinePrinter(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void writeRaw(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void encodeFragment(Unicode *text, int len, UnicodeMap *uMap, + GBool primaryLR, GString *s); + + // analysis + int rotateChars(GList *charsA); + void rotateUnderlinesAndLinks(int rot); + void unrotateChars(GList *charsA, int rot); + void unrotateColumns(GList *columns, int rot); + void unrotateWords(GList *words, int rot); + GBool checkPrimaryLR(GList *charsA); + void removeDuplicates(GList *charsA, int rot); + TextBlock *splitChars(GList *charsA); + TextBlock *split(GList *charsA, int rot); + GList *getChars(GList *charsA, double xMin, double yMin, + double xMax, double yMax); + void tagBlock(TextBlock *blk); + void insertLargeChars(GList *largeChars, TextBlock *blk); + void insertLargeCharsInFirstLeaf(GList *largeChars, TextBlock *blk); + void insertLargeCharInLeaf(TextChar *ch, TextBlock *blk); + void insertIntoTree(TextBlock *subtree, TextBlock *primaryTree); + void insertColumnIntoTree(TextBlock *column, TextBlock *tree); + void insertClippedChars(GList *clippedChars, TextBlock *tree); + TextBlock *findClippedCharLeaf(TextChar *ch, TextBlock *tree); + GList *buildColumns(TextBlock *tree); + void buildColumns2(TextBlock *blk, GList *columns); + TextColumn *buildColumn(TextBlock *tree); + double getLineIndent(TextLine *line, TextBlock *blk); + double getAverageLineSpacing(GList *lines); + double getLineSpacing(TextLine *line0, TextLine *line1); + void buildLines(TextBlock *blk, GList *lines); + TextLine *buildLine(TextBlock *blk); + void getLineChars(TextBlock *blk, GList *charsA); + double computeWordSpacingThreshold(GList *charsA, int rot); + int assignPhysLayoutPositions(GList *columns); + void assignLinePhysPositions(GList *columns); + void computeLinePhysWidth(TextLine *line, UnicodeMap *uMap); + int assignColumnPhysPositions(GList *columns); + void generateUnderlinesAndLinks(GList *columns); + + // debug +#if 0 //~debug + void dumpChars(GList *charsA); + void dumpTree(TextBlock *tree, int indent = 0); + void dumpColumns(GList *columns); +#endif - GBool rawOrder; // keep text in content stream order + TextOutputControl control; // formatting parameters double pageWidth, pageHeight; // width and height of current page - TextWord *curWord; // currently active string int charPos; // next character position (within content // stream) TextFontInfo *curFont; // current font double curFontSize; // current font size - int nest; // current nesting level (for Type 3 fonts) + int curRot; // current rotation int nTinyChars; // number of "tiny" chars seen so far - GBool lastCharOverlap; // set if the last added char overlapped the - // previous char Unicode *actualText; // current "ActualText" span int actualTextLen; double actualTextX0, @@ -521,32 +434,22 @@ private: actualTextY1; int actualTextNBytes; - TextPool *pools[4]; // a "pool" of TextWords for each rotation - TextFlow *flows; // linked list of flows - TextBlock **blocks; // array of blocks, in yx order - int nBlocks; // number of blocks - int primaryRot; // primary rotation - GBool primaryLR; // primary direction (true means L-to-R, - // false means R-to-L) - TextWord *rawWords; // list of words, in raw order (only if - // rawOrder is set) - TextWord *rawLastWord; // last word on rawWords list - + GList *chars; // [TextChar] GList *fonts; // all font info objects used on this // page [TextFontInfo] + GList *underlines; // [TextUnderline] + GList *links; // [TextLink] + + GList *findCols; // text used by the findText function + // [TextColumn] + GBool findLR; // primary text direction, used by the + // findText function double lastFindXMin, // coordinates of the last "find" result lastFindYMin; GBool haveLastFind; - GList *underlines; // [TextUnderline] - GList *links; // [TextLink] - - friend class TextLine; - friend class TextLineFrag; - friend class TextBlock; - friend class TextFlow; - friend class TextWordList; + friend class TextOutputDev; }; //------------------------------------------------------------------------ @@ -561,8 +464,7 @@ public: // <physLayoutA> is true, the original physical layout of the text // is maintained. If <rawOrder> is true, the text is kept in // content stream order. - TextOutputDev(char *fileName, GBool physLayoutA, - double fixedPitchA, GBool rawOrderA, + TextOutputDev(char *fileName, TextOutputControl *controlA, GBool append); // Create a TextOutputDev which will write to a generic stream. If @@ -570,8 +472,7 @@ public: // is maintained. If <rawOrder> is true, the text is kept in // content stream order. TextOutputDev(TextOutputFunc func, void *stream, - GBool physLayoutA, double fixedPitchA, - GBool rawOrderA); + TextOutputControl *controlA); // Destructor. virtual ~TextOutputDev(); @@ -660,20 +561,18 @@ public: double *xMin, double *yMin, double *xMax, double *yMax); -#if TEXTOUT_WORD_LIST // Build a flat word list, in content stream order (if // this->rawOrder is true), physical layout order (if // this->physLayout is true and this->rawOrder is false), or reading // order (if both flags are false). TextWordList *makeWordList(); -#endif // Returns the TextPage object for the last rasterized page, // transferring ownership to the caller. TextPage *takeText(); // Turn extra processing for HTML conversion on or off. - void enableHTMLExtras(GBool doHTMLA) { doHTML = doHTMLA; } + void enableHTMLExtras(GBool html) { control.html = html; } private: @@ -682,13 +581,7 @@ private: GBool needClose; // need to close the output file? // (only if outputStream is a FILE*) TextPage *text; // text for the current page - GBool physLayout; // maintain original physical layout when - // dumping text - double fixedPitch; // if physLayout is true and this is non-zero, - // assume fixed-pitch characters with this - // width - GBool rawOrder; // keep text in content stream order - GBool doHTML; // extra processing for HTML conversion + TextOutputControl control; // formatting parameters GBool ok; // set up ok? }; diff --git a/xpdf/TextString.cc b/xpdf/TextString.cc new file mode 100644 index 0000000..5bc9de2 --- /dev/null +++ b/xpdf/TextString.cc @@ -0,0 +1,164 @@ +//======================================================================== +// +// TextString.cc +// +// Copyright 2011-2013 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <string.h> +#include "gmem.h" +#include "GString.h" +#include "PDFDocEncoding.h" +#include "TextString.h" + +//------------------------------------------------------------------------ + +TextString::TextString() { + u = NULL; + len = size = 0; +} + +TextString::TextString(GString *s) { + u = NULL; + len = size = 0; + append(s); +} + +TextString::TextString(TextString *s) { + len = size = s->len; + if (len) { + u = (Unicode *)gmallocn(size, sizeof(Unicode)); + memcpy(u, s->u, len * sizeof(Unicode)); + } else { + u = NULL; + } +} + +TextString::~TextString() { + gfree(u); +} + +TextString *TextString::append(Unicode c) { + expand(1); + u[len] = c; + ++len; + return this; +} + +TextString *TextString::append(GString *s) { + int n, i; + + if ((s->getChar(0) & 0xff) == 0xfe && + (s->getChar(1) & 0xff) == 0xff) { + n = (s->getLength() - 2) / 2; + expand(n); + for (i = 0; i < n; ++i) { + u[len + i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | + (s->getChar(3 + 2*i) & 0xff); + } + len += n; + } else { + n = s->getLength(); + expand(n); + for (i = 0; i < n; ++i) { + u[len + i] = pdfDocEncoding[s->getChar(i) & 0xff]; + } + len += n; + } + return this; +} + +TextString *TextString::insert(int idx, Unicode c) { + if (idx >= 0 && idx <= len) { + expand(1); + if (idx < len) { + memmove(u + idx + 1, u + idx, (len - idx) * sizeof(Unicode)); + } + u[idx] = c; + ++len; + } + return this; +} + +TextString *TextString::insert(int idx, GString *s) { + int n, i; + + if (idx >= 0 && idx <= len) { + if ((s->getChar(0) & 0xff) == 0xfe && + (s->getChar(1) & 0xff) == 0xff) { + n = (s->getLength() - 2) / 2; + expand(n); + if (idx < len) { + memmove(u + idx + n, u + idx, (len - idx) * sizeof(Unicode)); + } + for (i = 0; i < n; ++i) { + u[idx + i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | + (s->getChar(3 + 2*i) & 0xff); + } + len += n; + } else { + n = s->getLength(); + expand(n); + if (idx < len) { + memmove(u + idx + n, u + idx, (len - idx) * sizeof(Unicode)); + } + for (i = 0; i < n; ++i) { + u[idx + i] = pdfDocEncoding[s->getChar(i) & 0xff]; + } + len += n; + } + } + return this; +} + +void TextString::expand(int delta) { + int newLen; + + newLen = len + delta; + if (delta > INT_MAX - len) { + // trigger an out-of-memory error + size = -1; + } else if (newLen <= size) { + return; + } else if (size > 0 && size <= INT_MAX / 2 && size*2 >= newLen) { + size *= 2; + } else { + size = newLen; + } + u = (Unicode *)greallocn(u, size, sizeof(Unicode)); +} + +GString *TextString::toPDFTextString() { + GString *s; + GBool useUnicode; + int i; + + useUnicode = gFalse; + for (i = 0; i < len; ++i) { + if (u[i] >= 0x80) { + useUnicode = gTrue; + break; + } + } + s = new GString(); + if (useUnicode) { + s->append((char)0xfe); + s->append((char)0xff); + for (i = 0; i < len; ++i) { + s->append((char)(u[i] >> 8)); + s->append((char)u[i]); + } + } else { + for (i = 0; i < len; ++i) { + s->append((char)u[i]); + } + } + return s; +} diff --git a/xpdf/TextString.h b/xpdf/TextString.h new file mode 100644 index 0000000..fac15e9 --- /dev/null +++ b/xpdf/TextString.h @@ -0,0 +1,66 @@ +//======================================================================== +// +// TextString.h +// +// Copyright 2011-2013 Glyph & Cog, LLC +// +// Represents a PDF "text string", which can either be a UTF-16BE +// string (with a leading byte order marker), or an 8-bit string in +// PDFDocEncoding. +// +//======================================================================== + +#ifndef TEXTSTRING_H +#define TEXTSTRING_H + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "CharTypes.h" + +class GString; + +//------------------------------------------------------------------------ + +class TextString { +public: + + // Create an empty TextString. + TextString(); + + // Create a TextString from a PDF text string. + TextString(GString *s); + + // Copy a TextString. + TextString(TextString *s); + + ~TextString(); + + // Append a Unicode character or PDF text string to this TextString. + TextString *append(Unicode c); + TextString *append(GString *s); + + // Insert a Unicode character or PDF text string in this TextString. + TextString *insert(int idx, Unicode c); + TextString *insert(int idx, GString *s); + + // Get the Unicode characters in the TextString. + int getLength() { return len; } + Unicode *getUnicode() { return u; } + + // Create a PDF text string from a TextString. + GString *toPDFTextString(); + +private: + + void expand(int delta); + + Unicode *u; // NB: not null-terminated + int len; + int size; +}; + +#endif diff --git a/xpdf/UnicodeTypeTable.cc b/xpdf/UnicodeTypeTable.cc index edd2970..34e68dc 100644 --- a/xpdf/UnicodeTypeTable.cc +++ b/xpdf/UnicodeTypeTable.cc @@ -2,7 +2,7 @@ // // UnicodeTypeTable.cc // -// Copyright 2004 Glyph & Cog, LLC +// Copyright 2004-2013 Glyph & Cog, LLC // //======================================================================== @@ -20,21 +20,21 @@ struct UnicodeCaseTableVector { }; static UnicodeMapTableEntry typeTable[256] = { - { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN###NNNNN################NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#N####NNNNLNNNNN####NLNNN#LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL", 'X' }, + { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN...NNNNN.....##########.NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN.N....NNNNLNNNNN..##NLNNN#LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL", 'X' }, { NULL, 'L' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLNNNNNNNNNNNNNNLLNNNNNNNNNNNNNNLLLLLNNNNNNNNNLNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLNNNNNNNNNNNLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNRNRNNRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' }, - { "RRRR#########RNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNN####################RRRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNRNNNNNNNRRNNNNNNNRR##########RRRRRR", 'X' }, + { "RRRR.........RNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNN#################.##RRRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNRNNNNNNNRRNNNNNNNRR##########RRRRRR", 'X' }, { "RRRRRRRRRRRRRRNNRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNNNNNNNNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNRNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { NULL, 'N' }, - { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLNNNNLLLLLLLLLLLLLNNLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNLLLLLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLL##LLLLLLLNNNNN", 'X' }, - { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLL##NNNNNNNNNNNNNN", 'X' }, - { "NNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLNLNNNLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNN#NLLLLL", 'X' }, + { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLNNNNLLLLLLLLLLLLLNNLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNLLLLLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLL..LLLLLLLNNNNN", 'X' }, + { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLL..NNNNNNNNNNNNNN", 'X' }, + { "NNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLNLNNNLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNN.NLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLLLLLLLNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, - { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNN#####LLLLLLLNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNNNNLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, + { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNN.....LLLLLLLNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNNNNLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNLNLNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNLNNNNNLNNLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLNNNNNNLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { NULL, 'L' }, @@ -43,7 +43,7 @@ static UnicodeMapTableEntry typeTable[256] = { { NULL, 'L' }, { NULL, 'L' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, - { "LLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLNLLNNNNNNNNNNNLLLLLLL#LNLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "LLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLNLLNNNNNNNNNNNLLLLLLL.LNLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLNNNNNLLLLLLNLLLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { NULL, 'L' }, @@ -52,9 +52,9 @@ static UnicodeMapTableEntry typeTable[256] = { { NULL, 'L' }, { NULL, 'L' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLNNNLLLLLLLLLLLNNNLLLLLLLLLLLLNNNNLLLLLLLLLLLLLNNNLLLLLLLLLLLLLNNN", 'X' }, - { "NNNNNNNNNNNNNNLRNNNNNNNNNNNNNNNNNNNNNNNNNNLRNLRN#####NNNNNNNNNNNNNNN#NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#L##########NNNL############NNN###################################NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, - { "NNLNNNNLNNLLLLLLLLLLNLNNNLLLLLNNNNNNLNLNLNLLLL#LLLNLLLLLLLNNLLLLNNNNNLLLLLNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, - { "NNNNNNNNNNNNNNNNNN##NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "NNNNNNNNNNNNNNLRNNNNNNNNNNNNNNNNNNNNNNNNNNLRNLRN.....NNNNNNNNNNNNNNN.NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#L########..NNNL##########..NNN...................................NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "NNLNNNNLNNLLLLLLLLLLNLNNNLLLLLNNNNNNLNLNLNLLLL.LLLNLLLLLLLNNLLLLNNNNNLLLLLNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "NNNNNNNNNNNNNNNNNN..NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN####################LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { NULL, 'N' }, @@ -271,11 +271,11 @@ static UnicodeMapTableEntry typeTable[256] = { { NULL, 'L' }, { NULL, 'L' }, { NULL, 'L' }, - { "LLLLLLLLLLLLLLLLLLLLLLLLRRRRRRNRRRRRRRRRR#RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' }, + { "LLLLLLLLLLLLLLLLLLLLLLLLRRRRRRNRRRRRRRRRR.RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' }, { NULL, 'R' }, { "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' }, - { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#N#NN#NNNNNNNNN#NN##NNNNN##NRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' }, - { "NNN###NNNNN################NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL#####NNN##NNNNNNNNNNNNNNNNNNNNNNNLL", 'X' } + { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN.N.NN.NNNNNNNNN.NN..NNNNN..NRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' }, + { "NNN...NNNNN.....##########.NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL.....NNN..NNNNNNNNNNNNNNNNNNNNNNNLL", 'X' } }; static UnicodeCaseTableVector caseTable00 = {{ @@ -935,13 +935,23 @@ GBool unicodeTypeR(Unicode c) { } GBool unicodeTypeNum(Unicode c) { - return getType(c) == '#'; + char t; + + t = getType(c); + return t == '#' || t == '.'; } GBool unicodeTypeAlphaNum(Unicode c) { char t; t = getType(c); + return t == 'L' || t == 'R' || t == '#' || t == '.'; +} + +GBool unicodeTypeWord(Unicode c) { + char t; + + t = getType(c); return t == 'L' || t == 'R' || t == '#'; } diff --git a/xpdf/UnicodeTypeTable.h b/xpdf/UnicodeTypeTable.h index 879363d..3230d92 100644 --- a/xpdf/UnicodeTypeTable.h +++ b/xpdf/UnicodeTypeTable.h @@ -2,7 +2,7 @@ // // UnicodeTypeTable.h // -// Copyright 2003 Glyph & Cog, LLC +// Copyright 2003-2013 Glyph & Cog, LLC // //======================================================================== @@ -19,6 +19,8 @@ extern GBool unicodeTypeNum(Unicode c); extern GBool unicodeTypeAlphaNum(Unicode c); +extern GBool unicodeTypeWord(Unicode c); + extern Unicode unicodeToUpper(Unicode c); #endif diff --git a/xpdf/XFAForm.cc b/xpdf/XFAForm.cc new file mode 100644 index 0000000..fd420a9 --- /dev/null +++ b/xpdf/XFAForm.cc @@ -0,0 +1,1458 @@ +//======================================================================== +// +// XFAForm.cc +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include "GString.h" +#include "GList.h" +#include "GHash.h" +#include "Error.h" +#include "Object.h" +#include "PDFDoc.h" +#include "Gfx.h" +#include "GfxFont.h" +#include "Zoox.h" +#include "XFAForm.h" + +#ifdef _WIN32 +# define strcasecmp stricmp +# define strncasecmp strnicmp +#endif + +//------------------------------------------------------------------------ + +// 5 bars + 5 spaces -- each can be wide (1) or narrow (0) +// (there are always exactly 3 wide elements; +// the last space is always narrow) +static Guchar code3Of9Data[128][10] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x00 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x10 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 1, 0, 0, 0, 1, 0, 0, 0 }, // ' ' = 0x20 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0 }, // '$' = 0x24 + { 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 }, // '%' = 0x25 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }, // '*' = 0x2a + { 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 }, // '+' = 0x2b + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 }, // '-' = 0x2d + { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, // '.' = 0x2e + { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, // '/' = 0x2f + { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, // '0' = 0x30 + { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // '1' + { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0 }, // '2' + { 1, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // '3' + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // '4' + { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // '5' + { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // '6' + { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, // '7' + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, // '8' + { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 }, // '9' + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x40 + { 1, 0, 0, 0, 0, 1, 0, 0, 1, 0 }, // 'A' = 0x41 + { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, // 'B' + { 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, // 'C' + { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, // 'D' + { 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // 'E' + { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // 'F' + { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0 }, // 'G' + { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, // 'H' + { 0, 0, 1, 0, 0, 1, 1, 0, 0, 0 }, // 'I' + { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // 'J' + { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 }, // 'K' + { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }, // 'L' + { 1, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, // 'M' + { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 }, // 'N' + { 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, // 'O' + { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, // 'P' = 0x50 + { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, // 'Q' + { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, // 'R' + { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 'S' + { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, // 'T' + { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, // 'U' + { 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 }, // 'V' + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // 'W' + { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 }, // 'X' + { 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 'Y' + { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // 'Z' + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x60 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x70 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +//------------------------------------------------------------------------ +// XFAForm +//------------------------------------------------------------------------ + +XFAForm *XFAForm::load(PDFDoc *docA, Object *acroFormObj, Object *xfaObj) { + XFAForm *xfaForm; + ZxDoc *xmlA; + ZxElement *tmpl; + Object catDict, resourceDictA, obj1; + GString *data; + GBool fullXFAA; + GString *name; + char buf[4096]; + int n, i; + + docA->getXRef()->getCatalog(&catDict); + catDict.dictLookup("NeedsRendering", &obj1); + fullXFAA = obj1.isBool() && obj1.getBool(); + obj1.free(); + catDict.free(); + + if (xfaObj->isStream()) { + data = new GString(); + xfaObj->streamReset(); + while ((n = xfaObj->getStream()->getBlock(buf, sizeof(buf))) > 0) { + data->append(buf, n); + } + } else if (xfaObj->isArray()) { + data = new GString(); + for (i = 1; i < xfaObj->arrayGetLength(); i += 2) { + if (!xfaObj->arrayGet(i, &obj1)->isStream()) { + error(errSyntaxError, -1, "XFA array element is wrong type"); + obj1.free(); + delete data; + return NULL; + } + obj1.streamReset(); + while ((n = obj1.getStream()->getBlock(buf, sizeof(buf))) > 0) { + data->append(buf, n); + } + obj1.free(); + } + } else { + error(errSyntaxError, -1, "XFA object is wrong type"); + return NULL; + } + + xmlA = ZxDoc::loadMem(data->getCString(), data->getLength()); + delete data; + if (!xmlA) { + error(errSyntaxError, -1, "Invalid XML in XFA form"); + return NULL; + } + + if (acroFormObj->isDict()) { + acroFormObj->dictLookup("DR", &resourceDictA); + } + + xfaForm = new XFAForm(docA, xmlA, &resourceDictA, fullXFAA); + + resourceDictA.free(); + + if (xfaForm->xml->getRoot()) { + if ((tmpl = xfaForm->xml->getRoot()->findFirstChildElement("template"))) { + name = new GString("form"); + xfaForm->curPageNum = 1; + xfaForm->curXOffset = xfaForm->curYOffset = 0; + xfaForm->scanFields(tmpl, name, name); + delete name; + } + } + + return xfaForm; +} + +XFAForm::XFAForm(PDFDoc *docA, ZxDoc *xmlA, Object *resourceDictA, + GBool fullXFAA): Form(docA) { + xml = xmlA; + fields = new GList(); + resourceDictA->copy(&resourceDict); + fullXFA = fullXFAA; +} + +XFAForm::~XFAForm() { + delete xml; + deleteGList(fields, XFAFormField); + resourceDict.free(); +} + +void XFAForm::scanFields(ZxElement *elem, GString *name, GString *dataName) { + ZxAttr *attr; + ZxNode *child; + ZxElement *bindElem; + GHash *names1, *names2; + GString *childName, *fullName, *fullDataName; + int i; + + //~ need to handle subform + + //~ need to handle exclGroup + //~ - fields in an exclGroup may/must(?) not have names + //~ - each field has an items element with the the value when that + //~ field is selected + + if (elem->isElement("field")) { + fields->append(new XFAFormField(this, elem, name->copy(), + dataName->copy(), curPageNum, + curXOffset, curYOffset)); + } else if (elem->isElement("breakBefore")) { + if ((attr = elem->findAttr("targetType")) && + !attr->getValue()->cmp("pageArea") && + (attr = elem->findAttr("startNew")) && + !attr->getValue()->cmp("1")) { + ++curPageNum; + } + } else if (elem->isElement("break")) { + if ((attr = elem->findAttr("before")) && + !attr->getValue()->cmp("pageArea") && + (attr = elem->findAttr("startNew")) && + !attr->getValue()->cmp("1")) { + ++curPageNum; + } + } else if (elem->isElement("contentArea")) { + curXOffset = XFAFormField::getMeasurement(elem->findAttr("x"), 0); + curYOffset = XFAFormField::getMeasurement(elem->findAttr("y"), 0); + } else { + names1 = new GHash(); + for (child = elem->getFirstChild(); child; child = child->getNextChild()) { + if (child->isElement() && + (attr = ((ZxElement *)child)->findAttr("name"))) { + childName = attr->getValue(); + names1->replace(childName, names1->lookupInt(childName) + 1); + } + } + names2 = new GHash(); + for (child = elem->getFirstChild(); child; child = child->getNextChild()) { + if (child->isElement()) { + if (!((bindElem = child->findFirstChildElement("bind")) && + (attr = bindElem->findAttr("match")) && + !attr->getValue()->cmp("none")) && + (attr = ((ZxElement *)child)->findAttr("name"))) { + childName = attr->getValue(); + if (names1->lookupInt(childName) > 1) { + i = names2->lookupInt(childName); + fullName = GString::format("{0:t}.{1:t}[{2:d}]", + name, childName, i); + fullDataName = GString::format("{0:t}.{1:t}[{2:d}]", + dataName, childName, i); + names2->replace(childName, i + 1); + } else { + fullName = GString::format("{0:t}.{1:t}", name, childName); + fullDataName = GString::format("{0:t}.{1:t}", dataName, childName); + } + } else { + fullName = name->copy(); + fullDataName = dataName->copy(); + } + scanFields((ZxElement *)child, fullName, fullDataName); + delete fullName; + delete fullDataName; + } + } + delete names1; + delete names2; + } +} + +void XFAForm::draw(int pageNum, Gfx *gfx, GBool printing) { + GfxFontDict *fontDict; + Object obj1; + int i; + + // build the font dictionary + if (resourceDict.isDict() && + resourceDict.dictLookup("Font", &obj1)->isDict()) { + fontDict = new GfxFontDict(doc->getXRef(), NULL, obj1.getDict()); + } else { + fontDict = NULL; + } + obj1.free(); + + for (i = 0; i < fields->getLength(); ++i) { + ((XFAFormField *)fields->get(i))->draw(pageNum, gfx, printing, fontDict); + } + + delete fontDict; +} + +int XFAForm::getNumFields() { + return fields->getLength(); +} + +FormField *XFAForm::getField(int idx) { + return (XFAFormField *)fields->get(idx); +} + +//------------------------------------------------------------------------ +// XFAFormField +//------------------------------------------------------------------------ + +XFAFormField::XFAFormField(XFAForm *xfaFormA, ZxElement *xmlA, GString *nameA, + GString *dataNameA, int pageNumA, + double xOffsetA, double yOffsetA) { + xfaForm = xfaFormA; + xml = xmlA; + name = nameA; + dataName = dataNameA; + pageNum = pageNumA; + xOffset = xOffsetA; + yOffset = yOffsetA; +} + +XFAFormField::~XFAFormField() { + delete name; + delete dataName; +} + +const char *XFAFormField::getType() { + ZxElement *uiElem; + ZxNode *node; + + if ((uiElem = xml->findFirstChildElement("ui"))) { + for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { + if (node->isElement("textEdit")) { + return "Text"; + } else if (node->isElement("barcode")) { + return "BarCode"; + } + //~ other field types go here + } + } + return NULL; +} + +Unicode *XFAFormField::getName(int *length) { + //~ assumes name is UTF-8 + return utf8ToUnicode(name, length); +} + +Unicode *XFAFormField::getValue(int *length) { + ZxElement *uiElem; + ZxNode *node; + GString *s; + + //~ assumes value is UTF-8 + s = NULL; + if ((uiElem = xml->findFirstChildElement("ui"))) { + for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { + if (node->isElement("textEdit")) { + s = getFieldValue("text"); + } else if (node->isElement("barcode")) { + s = getFieldValue("text"); + } + //~ other field types go here + } + } + if (!s) { + return NULL; + } + return utf8ToUnicode(s, length); +} + +Unicode *XFAFormField::utf8ToUnicode(GString *s, int *length) { + Unicode *u; + int n, size, c0, c1, c2, c3, c4, c5, i; + + n = size = 0; + u = NULL; + i = 0; + while (i < s->getLength()) { + if (n == size) { + size = size ? size * 2 : 16; + u = (Unicode *)greallocn(u, size, sizeof(Unicode)); + } + c0 = s->getChar(i++) & 0xff; + if (c0 <= 0x7f) { + u[n++] = c0; + } else if (c0 <= 0xdf && i < n) { + c1 = s->getChar(i++) & 0xff; + u[n++] = ((c0 & 0x1f) << 6) | (c1 & 0x3f); + } else if (c0 <= 0xef && i+1 < n) { + c1 = s->getChar(i++) & 0xff; + c2 = s->getChar(i++) & 0xff; + u[n++] = ((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f); + } else if (c0 <= 0xf7 && i+2 < n) { + c1 = s->getChar(i++) & 0xff; + c2 = s->getChar(i++) & 0xff; + c3 = s->getChar(i++) & 0xff; + u[n++] = ((c0 & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) + | (c3 & 0x3f); + } else if (c0 <= 0xfb && i+3 < n) { + c1 = s->getChar(i++) & 0xff; + c2 = s->getChar(i++) & 0xff; + c3 = s->getChar(i++) & 0xff; + c4 = s->getChar(i++) & 0xff; + u[n++] = ((c0 & 0x03) << 24) | ((c1 & 0x3f) << 18) | ((c2 & 0x3f) << 12) + | ((c3 & 0x3f) << 6) | (c4 & 0x3f); + } else if (c0 <= 0xfd && i+4 < n) { + c1 = s->getChar(i++) & 0xff; + c2 = s->getChar(i++) & 0xff; + c3 = s->getChar(i++) & 0xff; + c4 = s->getChar(i++) & 0xff; + c5 = s->getChar(i++) & 0xff; + u[n++] = ((c0 & 0x01) << 30) | ((c1 & 0x3f) << 24) | ((c2 & 0x3f) << 18) + | ((c3 & 0x3f) << 12) | ((c4 & 0x3f) << 6) | (c5 & 0x3f); + } else { + u[n++] = '?'; + } + } + *length = n; + return u; +} + +void XFAFormField::draw(int pageNumA, Gfx *gfx, GBool printing, + GfxFontDict *fontDict) { + Page *page; + PDFRectangle *pageRect; + ZxElement *uiElem; + ZxNode *node; + ZxAttr *attr; + GString *appearBuf; + MemStream *appearStream; + Object appearDict, appearance, obj1, obj2; + double mat[6]; + double x, y, w, h, x2, y2, w2, h2, x3, y3, w3, h3; + double anchorX, anchorY; + int pageRot, rot, rot3; + + if (pageNumA != pageNum) { + return; + } + + page = xfaForm->doc->getCatalog()->getPage(pageNum); + pageRect = page->getMediaBox(); + pageRot = page->getRotate(); + + anchorX = 0; + anchorY = 0; + if ((attr = xml->findAttr("anchorType"))) { + if (!attr->getValue()->cmp("topLeft")) { + anchorX = 0; + anchorY = 0; + } else if (!attr->getValue()->cmp("topCenter")) { + anchorX = 0.5; + anchorY = 0; + } else if (!attr->getValue()->cmp("topRight")) { + anchorX = 1; + anchorY = 0; + } else if (!attr->getValue()->cmp("middleLeft")) { + anchorX = 0; + anchorY = 0.5; + } else if (!attr->getValue()->cmp("middleCenter")) { + anchorX = 0.5; + anchorY = 0.5; + } else if (!attr->getValue()->cmp("middleRight")) { + anchorX = 1; + anchorY = 0.5; + } else if (!attr->getValue()->cmp("bottomLeft")) { + anchorX = 0; + anchorY = 1; + } else if (!attr->getValue()->cmp("bottomCenter")) { + anchorX = 0.5; + anchorY = 1; + } else if (!attr->getValue()->cmp("bottomRight")) { + anchorX = 1; + anchorY = 1; + } + } + x = getMeasurement(xml->findAttr("x"), 0) + xOffset; + y = getMeasurement(xml->findAttr("y"), 0) + yOffset; + w = getMeasurement(xml->findAttr("w"), 0); + h = getMeasurement(xml->findAttr("h"), 0); + if ((attr = xml->findAttr("rotate"))) { + rot = atoi(attr->getValue()->getCString()); + if ((rot %= 360) < 0) { + rot += 360; + } + } else { + rot = 0; + } + + // get annot rect (UL corner, width, height) in XFA coords + // notes: + // - XFA coordinates are top-left origin, after page rotation + // - XFA coordinates are dependent on choice of anchor point + // and field rotation + switch (rot) { + case 0: + default: + x2 = x - anchorX * w; + y2 = y - anchorY * h; + w2 = w; + h2 = h; + break; + case 90: + x2 = x - anchorY * h; + y2 = y - (1 - anchorX) * w; + w2 = h; + h2 = w; + break; + case 180: + x2 = x - (1 - anchorX) * w; + y2 = y - (1 - anchorY) * h; + w2 = w; + h2 = h; + break; + case 270: + x2 = x - (1 - anchorY) * h; + y2 = y - anchorX * w; + w2 = h; + h2 = w; + break; + } + + // convert annot rect to PDF coords (LL corner, width, height), + // taking page rotation into account + switch (pageRot) { + case 0: + default: + x3 = pageRect->x1 + x2; + y3 = pageRect->y2 - (y2 + h2); + w3 = w2; + h3 = h2; + break; + case 90: + x3 = pageRect->x1 + y2; + y3 = pageRect->y1 + x2; + w3 = h2; + h3 = w2; + break; + case 180: + x3 = pageRect->x2 - (x2 + w2); + y3 = pageRect->y1 + y2; + w3 = w2; + h3 = h2; + break; + case 270: + x3 = pageRect->x2 - (y2 + h2); + y3 = pageRect->y1 + (x2 + w2); + w3 = h2; + h3 = w2; + break; + } + rot3 = (rot + pageRot) % 360; + + // generate transform matrix + switch (rot3) { + case 0: + default: + mat[0] = 1; mat[1] = 0; + mat[2] = 0; mat[3] = 1; + mat[4] = 0; mat[5] = 0; + break; + case 90: + mat[0] = 0; mat[1] = 1; + mat[2] = -1; mat[3] = 0; + mat[4] = h; mat[5] = 0; + break; + case 180: + mat[0] = -1; mat[1] = 0; + mat[2] = 0; mat[3] = -1; + mat[4] = w; mat[5] = h; + break; + case 270: + mat[0] = 0; mat[1] = -1; + mat[2] = 1; mat[3] = 0; + mat[4] = 0; mat[5] = w; + break; + } + + // get the appearance stream data + appearBuf = new GString(); +#if 0 //~ for debugging + appearBuf->appendf("q 1 1 0 rg 0 0 {0:.4f} {1:.4f} re f Q\n", w, h); +#endif + if ((uiElem = xml->findFirstChildElement("ui"))) { + for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { + if (node->isElement("textEdit")) { + drawTextEdit(fontDict, w, h, rot3, appearBuf); + break; + } else if (node->isElement("barcode")) { + drawBarCode(fontDict, w, h, rot3, appearBuf); + break; + } + //~ other field types go here + } + } + + // create the appearance stream + appearDict.initDict(xfaForm->doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(xfaForm->doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(w)); + obj1.arrayAdd(obj2.initReal(h)); + appearDict.dictAdd(copyString("BBox"), &obj1); + obj1.initArray(xfaForm->doc->getXRef()); + obj1.arrayAdd(obj2.initReal(mat[0])); + obj1.arrayAdd(obj2.initReal(mat[1])); + obj1.arrayAdd(obj2.initReal(mat[2])); + obj1.arrayAdd(obj2.initReal(mat[3])); + obj1.arrayAdd(obj2.initReal(mat[4])); + obj1.arrayAdd(obj2.initReal(mat[5])); + appearDict.dictAdd(copyString("Matrix"), &obj1); + if (xfaForm->resourceDict.isDict()) { + appearDict.dictAdd(copyString("Resources"), + xfaForm->resourceDict.copy(&obj1)); + } + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.initStream(appearStream); + gfx->drawAnnot(&appearance, NULL, x3, y3, x3 + w3, y3 + h3); + appearance.free(); + delete appearBuf; +} + +void XFAFormField::drawTextEdit(GfxFontDict *fontDict, + double w, double h, int rot, + GString *appearBuf) { + ZxElement *valueElem, *textElem, *uiElem, *textEditElem, *combElem; + ZxElement *fontElem, *paraElem; + ZxAttr *attr; + GString *value, *fontName; + double fontSize; + int maxChars, combCells; + GBool multiLine, bold, italic; + XFAHorizAlign hAlign; + XFAVertAlign vAlign; + + if (!(value = getFieldValue("text"))) { + return; + } + + maxChars = 0; + if ((valueElem = xml->findFirstChildElement("value")) && + (textElem = valueElem->findFirstChildElement("text")) && + (attr = textElem->findAttr("maxChars"))) { + maxChars = atoi(attr->getValue()->getCString()); + } + + multiLine = gFalse; + combCells = 0; + if ((uiElem = xml->findFirstChildElement("ui")) && + (textEditElem = uiElem->findFirstChildElement("textEdit"))) { + if ((attr = textEditElem->findAttr("multiLine")) && + !attr->getValue()->cmp("1")) { + multiLine = gTrue; + } + if ((combElem = textEditElem->findFirstChildElement("comb"))) { + if ((attr = combElem->findAttr("numberOfCells"))) { + combCells = atoi(attr->getValue()->getCString()); + } else { + combCells = maxChars; + } + } + } + + fontName = NULL; + fontSize = 10; + bold = gFalse; + italic = gFalse; + if ((fontElem = xml->findFirstChildElement("font"))) { + if ((attr = fontElem->findAttr("typeface"))) { + fontName = attr->getValue()->copy(); + } + if ((attr = fontElem->findAttr("weight"))) { + if (!attr->getValue()->cmp("bold")) { + bold = gTrue; + } + } + if ((attr = fontElem->findAttr("posture"))) { + if (!attr->getValue()->cmp("italic")) { + italic = gTrue; + } + } + if ((attr = fontElem->findAttr("size"))) { + fontSize = getMeasurement(attr, fontSize); + } + } + if (!fontName) { + fontName = new GString("Courier"); + } + + hAlign = xfaHAlignLeft; + vAlign = xfaVAlignTop; + if ((paraElem = xml->findFirstChildElement("para"))) { + if ((attr = paraElem->findAttr("hAlign"))) { + if (!attr->getValue()->cmp("left")) { + hAlign = xfaHAlignLeft; + } else if (!attr->getValue()->cmp("center")) { + hAlign = xfaHAlignCenter; + } else if (!attr->getValue()->cmp("right")) { + hAlign = xfaHAlignRight; + } + //~ other hAlign values (justify, justifyAll, radix) are + //~ currently unsupported + } + if ((attr = paraElem->findAttr("vAlign"))) { + if (!attr->getValue()->cmp("top")) { + vAlign = xfaVAlignTop; + } else if (!attr->getValue()->cmp("bottom")) { + vAlign = xfaVAlignBottom; + } else if (!attr->getValue()->cmp("middle")) { + vAlign = xfaVAlignMiddle; + } + } + } + + drawText(value, multiLine, combCells, + fontName, bold, italic, fontSize, + hAlign, vAlign, 0, 0, w, h, gFalse, fontDict, appearBuf); + delete fontName; +} + +void XFAFormField::drawBarCode(GfxFontDict *fontDict, + double w, double h, int rot, + GString *appearBuf) { + ZxElement *uiElem, *barcodeElem, *fontElem; + ZxAttr *attr; + GString *value, *value2, *barcodeType, *textLocation, *fontName, *s1, *s2; + XFAVertAlign textAlign; + double wideNarrowRatio, fontSize; + double yText, wText, yBarcode, hBarcode, wNarrow, xx; + GBool doText; + int dataLength; + GBool bold, italic; + char *p; + int i, j, c; + + //--- get field value + if (!(value = getFieldValue("text"))) { + return; + } + + //--- get field attributes + barcodeType = NULL; + wideNarrowRatio = 3; + dataLength = 0; + textLocation = NULL; + if ((uiElem = xml->findFirstChildElement("ui")) && + (barcodeElem = uiElem->findFirstChildElement("barcode"))) { + if ((attr = barcodeElem->findAttr("type"))) { + barcodeType = attr->getValue(); + } + if ((attr = barcodeElem->findAttr("wideNarrowRatio"))) { + s1 = attr->getValue(); + if ((p = strchr(s1->getCString(), ':'))) { + s2 = new GString(s1, 0, p - s1->getCString()); + wideNarrowRatio = atof(p + 1); + if (wideNarrowRatio == 0) { + wideNarrowRatio = 1; + } + wideNarrowRatio = atof(s2->getCString()) / wideNarrowRatio; + delete s2; + } else { + wideNarrowRatio = atof(s1->getCString()); + } + } + if ((attr = barcodeElem->findAttr("dataLength"))) { + dataLength = atoi(attr->getValue()->getCString()); + } + if ((attr = barcodeElem->findAttr("textLocation"))) { + textLocation = attr->getValue(); + } + } + if (!barcodeType) { + error(errSyntaxError, -1, "Missing 'type' attribute in XFA barcode field"); + return; + } + if (!dataLength) { + error(errSyntaxError, -1, + "Missing 'dataLength' attribute in XFA barcode field"); + return; + } + + //--- get font + fontName = NULL; + fontSize = 0.2 * h; + bold = gFalse; + italic = gFalse; + if ((fontElem = xml->findFirstChildElement("font"))) { + if ((attr = fontElem->findAttr("typeface"))) { + fontName = attr->getValue()->copy(); + } + if ((attr = fontElem->findAttr("weight"))) { + if (!attr->getValue()->cmp("bold")) { + bold = gTrue; + } + } + if ((attr = fontElem->findAttr("posture"))) { + if (!attr->getValue()->cmp("italic")) { + italic = gTrue; + } + } + if ((attr = fontElem->findAttr("size"))) { + fontSize = getMeasurement(attr, fontSize); + } + } + if (!fontName) { + fontName = new GString("Courier"); + } + + //--- compute the embedded text type position + doText = gTrue; + yText = yBarcode = hBarcode = 0; + if (textLocation && !textLocation->cmp("above")) { + textAlign = xfaVAlignTop; + yText = h; + yBarcode = 0; + hBarcode = h - fontSize; + } else if (textLocation && !textLocation->cmp("belowEmbedded")) { + textAlign = xfaVAlignBottom; + yText = 0; + yBarcode = 0; + hBarcode = h; + } else if (textLocation && !textLocation->cmp("aboveEmbedded")) { + textAlign = xfaVAlignTop; + yText = h; + yBarcode = 0; + hBarcode = h; + } else if (textLocation && !textLocation->cmp("none")) { + textAlign = xfaVAlignBottom; // make gcc happy + doText = gFalse; + } else { // default is "below" + textAlign = xfaVAlignBottom; + yText = 0; + yBarcode = fontSize; + hBarcode = h - fontSize; + } + wText = w; + + //--- remove extraneous start/stop chars + //~ this may depend on barcode type + value2 = value->copy(); + if (value2->getLength() >= 1 && value2->getChar(0) == '*') { + value2->del(0); + } + if (value2->getLength() >= 1 && + value2->getChar(value2->getLength() - 1) == '*') { + value2->del(value2->getLength() - 1); + } + + //--- draw the bar code + if (!barcodeType->cmp("code3Of9")) { + appearBuf->append("0 g\n"); + wNarrow = w / ((7 + 3 * wideNarrowRatio) * (dataLength + 2)); + xx = 0; + for (i = -1; i <= value2->getLength(); ++i) { + if (i < 0 || i >= value2->getLength()) { + c = '*'; + } else { + c = value2->getChar(i) & 0x7f; + } + for (j = 0; j < 10; j += 2) { + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", + xx, yBarcode, + (code3Of9Data[c][j] ? wideNarrowRatio : 1) * wNarrow, + hBarcode); + xx += ((code3Of9Data[c][j] ? wideNarrowRatio : 1) + + (code3Of9Data[c][j+1] ? wideNarrowRatio : 1)) * wNarrow; + } + } + // center the text on the drawn barcode (not the max length barcode) + wText = (value2->getLength() + 2) * (7 + 3 * wideNarrowRatio) * wNarrow; + } else { + error(errSyntaxError, -1, + "Unimplemented barcode type in XFA barcode field"); + } + //~ add other barcode types here + + //--- draw the embedded text + if (doText) { + appearBuf->append("0 g\n"); + drawText(value2, gFalse, 0, + fontName, bold, italic, fontSize, + xfaHAlignCenter, textAlign, 0, yText, wText, h, gTrue, + fontDict, appearBuf); + } + delete fontName; + delete value2; +} + +Object *XFAFormField::getResources(Object *res) { + return xfaForm->resourceDict.copy(res); +} + +double XFAFormField::getMeasurement(ZxAttr *attr, double defaultVal) { + GString *s; + double val, mul; + GBool neg; + int i; + + if (!attr) { + return defaultVal; + } + s = attr->getValue(); + i = 0; + neg = gFalse; + if (i < s->getLength() && s->getChar(i) == '+') { + ++i; + } else if (i < s->getLength() && s->getChar(i) == '-') { + neg = gTrue; + ++i; + } + val = 0; + while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') { + val = val * 10 + s->getChar(i) - '0'; + ++i; + } + if (i < s->getLength() && s->getChar(i) == '.') { + ++i; + mul = 0.1; + while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') { + val += mul * (s->getChar(i) - '0'); + mul *= 0.1; + ++i; + } + } + if (neg) { + val = -val; + } + if (i+1 < s->getLength()) { + if (s->getChar(i) == 'i' && s->getChar(i+1) == 'n') { + val *= 72; + } else if (s->getChar(i) == 'p' && s->getChar(i+1) == 't') { + // no change + } else if (s->getChar(i) == 'c' && s->getChar(i+1) == 'm') { + val *= 72 / 2.54; + } else if (s->getChar(i) == 'm' && s->getChar(i+1) == 'm') { + val *= 72 / 25.4; + } else { + // default to inches + val *= 72; + } + } else { + // default to inches + val *= 72; + } + return val; +} + +GString *XFAFormField::getFieldValue(const char *valueChildType) { + ZxElement *valueElem, *datasets, *data, *elem; + char *p; + + // check the <value> element within the field + if ((valueElem = xml->findFirstChildElement("value")) && + (elem = valueElem->findFirstChildElement(valueChildType))) { + if (elem->getFirstChild() && + elem->getFirstChild()->isCharData() && + ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) { + return ((ZxCharData *)elem->getFirstChild())->getData(); + } + } + + // check the <datasets> packet + if (!xfaForm->xml->getRoot() || + !(datasets = + xfaForm->xml->getRoot()->findFirstChildElement("xfa:datasets")) || + !(data = datasets->findFirstChildElement("xfa:data"))) { + return NULL; + } + p = name->getCString(); + if (!strncmp(p, "form.", 5)) { + p += 5; + } else { + return NULL; + } + elem = findFieldData(data, p); + if (elem && + elem->getFirstChild() && + elem->getFirstChild()->isCharData() && + ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) { + return ((ZxCharData *)elem->getFirstChild())->getData(); + } + + return NULL; +} + +ZxElement *XFAFormField::findFieldData(ZxElement *elem, char *partName) { + ZxNode *node; + GString *nodeName; + int curIdx, idx, n; + + curIdx = 0; + for (node = elem->getFirstChild(); node; node = node->getNextChild()) { + if (node->isElement()) { + nodeName = ((ZxElement *)node)->getType(); + n = nodeName->getLength(); + if (!strncmp(partName, nodeName->getCString(), n)) { + if (partName[n] == '[') { + idx = atoi(partName + n + 1); + if (idx == curIdx) { + for (++n; partName[n] && partName[n-1] != ']'; ++n) ; + } else { + ++curIdx; + continue; + } + } + if (!partName[n]) { + return (ZxElement *)node; + } else if (partName[n] == '.') { + return findFieldData((ZxElement *)node, partName + n + 1); + } + } + } + } + return NULL; +} + +void XFAFormField::transform(int rot, double w, double h, + double *wNew, double *hNew, GString *appearBuf) { + switch (rot) { + case 0: + default: + appearBuf->appendf("1 0 0 1 0 {0:.4f} cm\n", -h); + break; + case 90: + appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", w); + *wNew = h; + *hNew = w; + break; + case 180: + appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", w, h); + *wNew = w; + *hNew = h; + break; + case 270: + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", h); + *wNew = h; + *hNew = w; + break; + } +} + +void XFAFormField::drawText(GString *text, GBool multiLine, int combCells, + GString *fontName, GBool bold, + GBool italic, double fontSize, + XFAHorizAlign hAlign, XFAVertAlign vAlign, + double x, double y, double w, double h, + GBool whiteBackground, + GfxFontDict *fontDict, GString *appearBuf) { + GfxFont *font; + GString *s; + double xx, yy, tw, charWidth, lineHeight; + double rectX, rectY, rectW, rectH; + int line, i, j, k, c, rectI; + + //~ deal with Unicode text (is it UTF-8?) + + // find the font + if (!(font = findFont(fontDict, fontName, bold, italic))) { + error(errSyntaxError, -1, "Couldn't find a font for '{0:t}', {1:s}, {2:s} used in XFA field", + fontName, bold ? "bold" : "non-bold", + italic ? "italic" : "non-italic"); + return; + } + + // setup + rectW = rectH = 0; + rectI = appearBuf->getLength(); + appearBuf->append("BT\n"); + appearBuf->appendf("/{0:t} {1:.2f} Tf\n", font->getTag(), fontSize); + + // multi-line text + if (multiLine) { + + // figure out how many lines will fit + lineHeight = 1.2 * fontSize; + + // write a series of lines of text + line = 0; + i = 0; + while (i < text->getLength()) { + + getNextLine(text, i, font, fontSize, w, &j, &tw, &k); + if (tw > rectW) { + rectW = tw; + } + + // compute text start position + switch (hAlign) { + case xfaHAlignLeft: + default: + xx = x; + break; + case xfaHAlignCenter: + xx = x + 0.5 * (w - tw); + break; + case xfaHAlignRight: + xx = x + w - tw; + break; + } + yy = y + h - fontSize * font->getAscent() - line * lineHeight; + + // draw the line + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", xx, yy); + appearBuf->append('('); + for (; i < j; ++i) { + c = text->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + + // next line + i = k; + ++line; + } + rectH = line * lineHeight; + rectY = y + h - rectH; + + // comb formatting + } else if (combCells > 0) { + + // compute comb spacing + tw = w / combCells; + + // compute text start position + switch (hAlign) { + case xfaHAlignLeft: + default: + xx = x; + break; + case xfaHAlignCenter: + xx = x + (int)(0.5 * (combCells - text->getLength())) * tw; + break; + case xfaHAlignRight: + xx = x + w - text->getLength() * tw; + break; + } + rectW = text->getLength() * tw; + switch (vAlign) { + case xfaVAlignTop: + default: + yy = y + h - fontSize * font->getAscent(); + break; + case xfaVAlignMiddle: + yy = y + 0.5 * (h - fontSize * (font->getAscent() + + font->getDescent())); + break; + case xfaVAlignBottom: + yy = y - fontSize * font->getDescent(); + break; + } + rectY = yy + fontSize * font->getDescent(); + rectH = fontSize * (font->getAscent() - font->getDescent()); + + // write the text string + for (i = 0; i < text->getLength(); ++i) { + c = text->getChar(i) & 0xff; + if (!font->isCIDFont()) { + charWidth = fontSize * ((Gfx8BitFont *)font)->getWidth(c); + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", + xx + i * tw + 0.5 * (tw - charWidth), yy); + } else { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", + xx + i * tw, yy); + } + appearBuf->append('('); + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("{0:.4f} 0 Td\n", w); + } else { + appearBuf->append(c); + } + appearBuf->append(") Tj\n"); + } + + // regular (non-comb) formatting + } else { + + // compute string width + if (!font->isCIDFont()) { + tw = 0; + for (i = 0; i < text->getLength(); ++i) { + tw += ((Gfx8BitFont *)font)->getWidth(text->getChar(i)); + } + } else { + // otherwise, make a crude estimate + tw = text->getLength() * 0.5; + } + tw *= fontSize; + rectW = tw; + + // compute text start position + switch (hAlign) { + case xfaHAlignLeft: + default: + xx = x; + break; + case xfaHAlignCenter: + xx = x + 0.5 * (w - tw); + break; + case xfaHAlignRight: + xx = x + w - tw; + break; + } + switch (vAlign) { + case xfaVAlignTop: + default: + yy = y + h - fontSize * font->getAscent(); + break; + case xfaVAlignMiddle: + yy = y + 0.5 * (h - fontSize * (font->getAscent() + + font->getDescent())); + break; + case xfaVAlignBottom: + yy = y - fontSize * font->getDescent(); + break; + } + rectY = yy + fontSize * font->getDescent(); + rectH = fontSize * (font->getAscent() - font->getDescent()); + appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx, yy); + + // write the text string + appearBuf->append('('); + for (i = 0; i < text->getLength(); ++i) { + c = text->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + } + + // cleanup + appearBuf->append("ET\n"); + + // draw a white rectangle behind the text + if (whiteBackground) { + switch (hAlign) { + case xfaHAlignLeft: + default: + rectX = x; + break; + case xfaHAlignCenter: + rectX = x + 0.5 * (w - rectW); + break; + case xfaHAlignRight: + rectX = x + w - rectW; + break; + } + rectX -= 0.25 * fontSize; + rectW += 0.5 * fontSize; + s = GString::format("q 1 g {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n", + rectX, rectY, rectW, rectH); + appearBuf->insert(rectI, s); + delete s; + } +} + +// Searches <fontDict> for a font matching(<fontName>, <bold>, +// <italic>). +GfxFont *XFAFormField::findFont(GfxFontDict *fontDict, GString *fontName, + GBool bold, GBool italic) { + GString *reqName, *testName; + GfxFont *font; + GBool foundName, foundBold, foundItalic; + char *p; + char c; + int i, j; + + if (!fontDict) { + return NULL; + } + + reqName = new GString(); + for (i = 0; i < fontName->getLength(); ++i) { + c = fontName->getChar(i); + if (c != ' ') { + reqName->append(c); + } + } + + for (i = 0; i < fontDict->getNumFonts(); ++i) { + font = fontDict->getFont(i); + if (!font || !font->getName()) { + continue; + } + testName = new GString(); + for (j = 0; j < font->getName()->getLength(); ++j) { + c = font->getName()->getChar(j); + if (c != ' ') { + testName->append(c); + } + } + foundName = foundBold = foundItalic = gFalse; + for (p = testName->getCString(); *p; ++p) { + if (!strncasecmp(p, reqName->getCString(), reqName->getLength())) { + foundName = gTrue; + } + if (!strncasecmp(p, "bold", 4)) { + foundBold = gTrue; + } + if (!strncasecmp(p, "italic", 6) || !strncasecmp(p, "oblique", 7)) { + foundItalic = gTrue; + } + } + delete testName; + if (foundName && foundBold == bold && foundItalic == italic) { + delete reqName; + return font; + } + } + + delete reqName; + return NULL; +} + +// Figure out how much text will fit on the next line. Returns: +// *end = one past the last character to be included +// *width = width of the characters start .. end-1 +// *next = index of first character on the following line +void XFAFormField::getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next) { + double w, dw; + int j, k, c; + + // figure out how much text will fit on the line + //~ what does Adobe do with tabs? + w = 0; + for (j = start; j < text->getLength() && w <= wMax; ++j) { + c = text->getChar(j) & 0xff; + if (c == 0x0a || c == 0x0d) { + break; + } + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + if (w > wMax) { + for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; + for (; k > start && text->getChar(k-1) == ' '; --k) ; + if (k > start) { + j = k; + } + if (j == start) { + // handle the pathological case where the first character is + // too wide to fit on the line all by itself + j = start + 1; + } + } + *end = j; + + // compute the width + w = 0; + for (k = start; k < j; ++k) { + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + *width = w; + + // next line + while (j < text->getLength() && text->getChar(j) == ' ') { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0d) { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0a) { + ++j; + } + *next = j; +} diff --git a/xpdf/XFAForm.h b/xpdf/XFAForm.h new file mode 100644 index 0000000..95bf14c --- /dev/null +++ b/xpdf/XFAForm.h @@ -0,0 +1,126 @@ +//======================================================================== +// +// XFAForm.h +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef XFAFORM_H +#define XFAFORM_H + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "Form.h" + +class ZxDoc; +class ZxElement; +class ZxAttr; + +//------------------------------------------------------------------------ + +enum XFAHorizAlign { + xfaHAlignLeft, + xfaHAlignCenter, + xfaHAlignRight +}; + +enum XFAVertAlign { + xfaVAlignTop, + xfaVAlignBottom, + xfaVAlignMiddle +}; + +//------------------------------------------------------------------------ + +class XFAForm: public Form { +public: + + static XFAForm *load(PDFDoc *docA, Object *acroFormObj, Object *xfaObj); + + virtual ~XFAForm(); + + virtual const char *getType() { return "XFA"; } + + virtual void draw(int pageNum, Gfx *gfx, GBool printing); + + virtual int getNumFields(); + virtual FormField *getField(int idx); + +private: + + XFAForm(PDFDoc *docA, ZxDoc *xmlA, Object *resourceDictA, GBool fullXFAA); + void scanFields(ZxElement *elem, GString *name, GString *dataName); + + ZxDoc *xml; + GList *fields; // [XFAFormField] + Object resourceDict; + GBool fullXFA; // true for "Full XFA", false for + // "XFA Foreground" + int curPageNum; // current page number - used by scanFields() + double curXOffset, // current x,y offset - used by scanFields() + curYOffset; + + friend class XFAFormField; +}; + +//------------------------------------------------------------------------ + +class XFAFormField: public FormField { +public: + + XFAFormField(XFAForm *xfaFormA, ZxElement *xmlA, GString *nameA, + GString *dataNameA, int pageNumA, + double xOffsetA, double yOffsetA); + + virtual ~XFAFormField(); + + virtual const char *getType(); + virtual Unicode *getName(int *length); + virtual Unicode *getValue(int *length); + + virtual Object *getResources(Object *res); + +private: + + Unicode *utf8ToUnicode(GString *s, int *length); + void draw(int pageNumA, Gfx *gfx, GBool printing, GfxFontDict *fontDict); + void drawTextEdit(GfxFontDict *fontDict, + double w, double h, int rot, + GString *appearBuf); + void drawBarCode(GfxFontDict *fontDict, + double w, double h, int rot, + GString *appearBuf); + static double getMeasurement(ZxAttr *attr, double defaultVal); + GString *getFieldValue(const char *valueChildType); + ZxElement *findFieldData(ZxElement *elem, char *partName); + void transform(int rot, double w, double h, + double *wNew, double *hNew, GString *appearBuf); + void drawText(GString *text, GBool multiLine, int combCells, + GString *fontName, GBool bold, + GBool italic, double fontSize, + XFAHorizAlign hAlign, XFAVertAlign vAlign, + double x, double y, double w, double h, + GBool whiteBackground, + GfxFontDict *fontDict, GString *appearBuf); + GfxFont *findFont(GfxFontDict *fontDict, GString *fontName, + GBool bold, GBool italic); + void getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next); + + XFAForm *xfaForm; + ZxElement *xml; + GString *name; + GString *dataName; + int pageNum; + double xOffset, yOffset; + + friend class XFAForm; +}; + +#endif diff --git a/xpdf/XPDFCore.cc b/xpdf/XPDFCore.cc index b98bc37..9a3725f 100644 --- a/xpdf/XPDFCore.cc +++ b/xpdf/XPDFCore.cc @@ -493,7 +493,7 @@ void XPDFCore::doAction(LinkAction *action) { } s = ((LinkGoToR *)action)->getFileName()->getCString(); //~ translate path name for VMS (deal with '/') - if (isAbsolutePath(s)) { + if (isAbsolutePath(s) || !doc->getFileName()) { fileName = new GString(s); } else { fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s); @@ -531,7 +531,7 @@ void XPDFCore::doAction(LinkAction *action) { if (!strcmp(s + fileName->getLength() - 4, ".pdf") || !strcmp(s + fileName->getLength() - 4, ".PDF")) { //~ translate path name for VMS (deal with '/') - if (isAbsolutePath(s)) { + if (isAbsolutePath(s) || !doc->getFileName()) { fileName = fileName->copy(); } else { fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s); @@ -640,7 +640,8 @@ void XPDFCore::doAction(LinkAction *action) { if (movieAnnot.dictLookup("Movie", &obj1)->isDict()) { if (obj1.dictLookup("F", &obj2)) { if ((fileName = LinkAction::getFileSpecName(&obj2))) { - if (!isAbsolutePath(fileName->getCString())) { + if (!isAbsolutePath(fileName->getCString()) && + doc->getFileName()) { fileName2 = appendToPath( grabPath(doc->getFileName()->getCString()), fileName->getCString()); @@ -658,6 +659,13 @@ void XPDFCore::doAction(LinkAction *action) { movieAnnot.free(); break; + // unsupported action types + case actionJavaScript: + case actionSubmitForm: + case actionHide: + error(errSyntaxError, -1, "Unsupported link action type"); + break; + // unknown action type case actionUnknown: error(errSyntaxError, -1, "Unknown link action type: '{0:t}'", @@ -1124,6 +1132,9 @@ void XPDFCore::inputCbk(Widget widget, XtPointer ptr, XtPointer callData) { case actionMovie: s = "[movie]"; break; + case actionJavaScript: + case actionSubmitForm: + case actionHide: case actionUnknown: s = "[unknown link]"; break; @@ -1356,6 +1367,8 @@ void XPDFCore::redrawRect(PDFCoreTile *tileA, int xSrc, int ySrc, XFillRectangle(display, drawAreaWin, drawAreaGC, xDest, yDest, width, height); } + + XFlush(display); } void XPDFCore::updateScrollbars() { diff --git a/xpdf/XPDFViewer.cc b/xpdf/XPDFViewer.cc index 2de349d..4292a0e 100644 --- a/xpdf/XPDFViewer.cc +++ b/xpdf/XPDFViewer.cc @@ -165,6 +165,7 @@ XPDFViewerCmd XPDFViewer::cmdTab[] = { { "about", 0, gFalse, gFalse, &XPDFViewer::cmdAbout }, { "closeOutline", 0, gFalse, gFalse, &XPDFViewer::cmdCloseOutline }, { "closeWindow", 0, gFalse, gFalse, &XPDFViewer::cmdCloseWindow }, + { "closeWindowOrQuit", 0, gFalse, gFalse, &XPDFViewer::cmdCloseWindowOrQuit }, { "continuousMode", 0, gFalse, gFalse, &XPDFViewer::cmdContinuousMode }, { "endPan", 0, gTrue, gTrue, &XPDFViewer::cmdEndPan }, { "endSelection", 0, gTrue, gTrue, &XPDFViewer::cmdEndSelection }, @@ -384,7 +385,9 @@ void XPDFViewer::open(GString *fileName, int pageA, GString *destName) { int pg; double z; - if (!core->getDoc() || fileName->cmp(core->getDoc()->getFileName())) { + if (!core->getDoc() || + !core->getDoc()->getFileName() || + fileName->cmp(core->getDoc()->getFileName())) { if (!loadFile(fileName, NULL, NULL)) { return; } @@ -445,7 +448,7 @@ GBool XPDFViewer::loadFile(GString *fileName, GString *ownerPassword, void XPDFViewer::reloadFile() { int pg; - if (!core->getDoc()) { + if (!core->getDoc() || !core->getDoc()->getFileName()) { return; } pg = core->getPageNum(); @@ -808,6 +811,11 @@ void XPDFViewer::cmdCloseWindow(GString *args[], int nArgs, app->close(this, gFalse); } +void XPDFViewer::cmdCloseWindowOrQuit(GString *args[], int nArgs, + XEvent *event) { + app->close(this, gTrue); +} + void XPDFViewer::cmdContinuousMode(GString *args[], int nArgs, XEvent *event) { Widget btn; @@ -1803,7 +1811,7 @@ void XPDFViewer::initToolbar(Widget parent) { menuPane = XmCreatePulldownMenu(toolBar, "zoomMenuPane", args, n); for (i = 0; i < nZoomMenuItems; ++i) { n = 0; - s = XmStringCreateLocalized(zoomMenuInfo[i].label); + s = XmStringCreateLocalized((char *)zoomMenuInfo[i].label); XtSetArg(args[n], XmNlabelString, s); ++n; XtSetArg(args[n], XmNuserData, (XtPointer)i); ++n; sprintf(buf, "zoom%d", i); @@ -3422,17 +3430,18 @@ void XPDFViewer::setupPrintDialog() { doc = core->getDoc(); psFileName = globalParams->getPSFile(); if (!psFileName || psFileName->getChar(0) == '|') { - pdfFileName = doc->getFileName(); - p = pdfFileName->getCString() + pdfFileName->getLength() - 4; - if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")) { - psFileName2 = new GString(pdfFileName->getCString(), - pdfFileName->getLength() - 4); - } else { - psFileName2 = pdfFileName->copy(); + if ((pdfFileName = doc->getFileName())) { + p = pdfFileName->getCString() + pdfFileName->getLength() - 4; + if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")) { + psFileName2 = new GString(pdfFileName->getCString(), + pdfFileName->getLength() - 4); + } else { + psFileName2 = pdfFileName->copy(); + } + psFileName2->append(".ps"); + XmTextFieldSetString(printFileText, psFileName2->getCString()); + delete psFileName2; } - psFileName2->append(".ps"); - XmTextFieldSetString(printFileText, psFileName2->getCString()); - delete psFileName2; } if (psFileName && psFileName->getChar(0) == '|') { XmToggleButtonSetState(printWithCmdBtn, True, False); diff --git a/xpdf/XPDFViewer.h b/xpdf/XPDFViewer.h index 8a345e8..8b03e7f 100644 --- a/xpdf/XPDFViewer.h +++ b/xpdf/XPDFViewer.h @@ -103,6 +103,7 @@ private: void cmdAbout(GString *args[], int nArgs, XEvent *event); void cmdCloseOutline(GString *args[], int nArgs, XEvent *event); void cmdCloseWindow(GString *args[], int nArgs, XEvent *event); + void cmdCloseWindowOrQuit(GString *args[], int nArgs, XEvent *event); void cmdContinuousMode(GString *args[], int nArgs, XEvent *event); void cmdEndPan(GString *args[], int nArgs, XEvent *event); void cmdEndSelection(GString *args[], int nArgs, XEvent *event); diff --git a/xpdf/XRef.cc b/xpdf/XRef.cc index 83322c3..71540a9 100644 --- a/xpdf/XRef.cc +++ b/xpdf/XRef.cc @@ -18,6 +18,7 @@ #include <ctype.h> #include <limits.h> #include "gmem.h" +#include "gfile.h" #include "Object.h" #include "Stream.h" #include "Lexer.h" @@ -43,6 +44,84 @@ #define defPermFlags 0xfffc //------------------------------------------------------------------------ +// XRefPosSet +//------------------------------------------------------------------------ + +class XRefPosSet { +public: + + XRefPosSet(); + ~XRefPosSet(); + void add(GFileOffset pos); + GBool check(GFileOffset pos); + +private: + + int find(GFileOffset pos); + + GFileOffset *tab; + int size; + int len; +}; + +XRefPosSet::XRefPosSet() { + size = 16; + len = 0; + tab = (GFileOffset *)gmallocn(size, sizeof(GFileOffset)); +} + +XRefPosSet::~XRefPosSet() { + gfree(tab); +} + +void XRefPosSet::add(GFileOffset pos) { + int i; + + i = find(pos); + if (i < len && tab[i] == pos) { + return; + } + if (len == size) { + if (size > INT_MAX / 2) { + gMemError("Integer overflow in XRefPosSet::add()"); + } + size *= 2; + tab = (GFileOffset *)greallocn(tab, size, sizeof(GFileOffset)); + } + if (i < len) { + memmove(&tab[i + 1], &tab[i], (len - i) * sizeof(GFileOffset)); + } + tab[i] = pos; + ++len; +} + +GBool XRefPosSet::check(GFileOffset pos) { + int i; + + i = find(pos); + return i < len && tab[i] == pos; +} + +int XRefPosSet::find(GFileOffset pos) { + int a, b, m; + + a = - 1; + b = len; + // invariant: tab[a] < pos < tab[b] + while (b - a > 1) { + m = (a + b) / 2; + if (tab[m] < pos) { + a = m; + } else if (tab[m] > pos) { + b = m; + } else { + return m; + } + } + return b; +} + +//------------------------------------------------------------------------ // ObjectStream //------------------------------------------------------------------------ @@ -134,7 +213,7 @@ ObjectStream::ObjectStream(XRef *xref, int objStrNumA) { obj2.free(); delete parser; gfree(offsets); - goto err1; + goto err2; } objNums[i] = obj1.getInt(); offsets[i] = obj2.getInt(); @@ -144,7 +223,7 @@ ObjectStream::ObjectStream(XRef *xref, int objStrNumA) { (i > 0 && offsets[i] < offsets[i-1])) { delete parser; gfree(offsets); - goto err1; + goto err2; } } while (str->getChar() != EOF) ; @@ -153,8 +232,8 @@ ObjectStream::ObjectStream(XRef *xref, int objStrNumA) { // skip to the first object - this shouldn't be necessary because // the First key is supposed to be equal to offsets[0], but just in // case... - for (i = first; i < offsets[0]; ++i) { - objStr.getStream()->getChar(); + if (i < offsets[0]) { + objStr.getStream()->discardChars(offsets[0] - i); } // parse the objects @@ -175,6 +254,8 @@ ObjectStream::ObjectStream(XRef *xref, int objStrNumA) { gfree(offsets); ok = gTrue; + err2: + objStr.streamClose(); err1: objStr.free(); } @@ -203,8 +284,10 @@ Object *ObjectStream::getObject(int objIdx, int objNum, Object *obj) { //------------------------------------------------------------------------ XRef::XRef(BaseStream *strA, GBool repair) { - Guint pos; + GFileOffset pos; Object obj; + XRefPosSet *posSet; + int i; ok = gTrue; errCode = errNone; @@ -213,12 +296,18 @@ XRef::XRef(BaseStream *strA, GBool repair) { entries = NULL; streamEnds = NULL; streamEndsLen = 0; - objStr = NULL; + for (i = 0; i < objStrCacheSize; ++i) { + objStrs[i] = NULL; + } encrypted = gFalse; permFlags = defPermFlags; ownerPasswordOk = gFalse; + for (i = 0; i < xrefCacheSize; ++i) { + cache[i].num = -1; + } + str = strA; start = str->getStart(); @@ -241,7 +330,9 @@ XRef::XRef(BaseStream *strA, GBool repair) { } // read the xref table - while (readXRef(&pos)) ; + posSet = new XRefPosSet(); + while (readXRef(&pos, posSet)) ; + delete posSet; if (!ok) { errCode = errDamaged; return; @@ -268,30 +359,34 @@ XRef::XRef(BaseStream *strA, GBool repair) { } XRef::~XRef() { + int i; + + for (i = 0; i < xrefCacheSize; ++i) { + if (cache[i].num >= 0) { + cache[i].obj.free(); + } + } gfree(entries); trailerDict.free(); if (streamEnds) { gfree(streamEnds); } - if (objStr) { - delete objStr; + for (i = 0; i < objStrCacheSize; ++i) { + if (objStrs[i]) { + delete objStrs[i]; + } } } // Read the 'startxref' position. -Guint XRef::getStartXref() { +GFileOffset XRef::getStartXref() { char buf[xrefSearchSize+1]; char *p; - int c, n, i; + int n, i; // read last xrefSearchSize bytes str->setPos(xrefSearchSize, -1); - for (n = 0; n < xrefSearchSize; ++n) { - if ((c = str->getChar()) == EOF) { - break; - } - buf[n] = c; - } + n = str->getBlock(buf, xrefSearchSize); buf[n] = '\0'; // find startxref @@ -304,86 +399,123 @@ Guint XRef::getStartXref() { return 0; } for (p = &buf[i+9]; isspace(*p & 0xff); ++p) ; - lastXRefPos = strToUnsigned(p); + lastXRefPos = strToFileOffset(p); return lastXRefPos; } // Read one xref table section. Also reads the associated trailer // dictionary, and returns the prev pointer (if any). -GBool XRef::readXRef(Guint *pos) { +GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet) { Parser *parser; Object obj; GBool more; + char buf[100]; + int n, i; - // start up a parser, parse one token - obj.initNull(); - parser = new Parser(NULL, - new Lexer(NULL, - str->makeSubStream(start + *pos, gFalse, 0, &obj)), - gTrue); - parser->getObj(&obj, gTrue); + // the xref data should either be "xref ..." (for an xref table) or + // "nn gg obj << ... >> stream ..." (for an xref stream); possibly + // preceded by whitespace + str->setPos(start + *pos); + n = str->getBlock(buf, 100); + for (i = 0; i < n && Lexer::isSpace(buf[i]); ++i) ; // parse an old-style xref table - if (obj.isCmd("xref")) { - obj.free(); - more = readXRefTable(parser, pos); + if (i + 4 < n && + buf[i] == 'x' && buf[i+1] == 'r' && buf[i+2] == 'e' && buf[i+3] == 'f' && + Lexer::isSpace(buf[i+4])) { + more = readXRefTable(pos, i + 5, posSet); // parse an xref stream - } else if (obj.isInt()) { + } else if (i < n && buf[i] >= '0' && buf[i] <= '9') { + obj.initNull(); + parser = new Parser(NULL, + new Lexer(NULL, + str->makeSubStream(start + *pos, gFalse, 0, &obj)), + gTrue); + if (!parser->getObj(&obj, gTrue)->isInt()) { + goto err2; + } obj.free(); if (!parser->getObj(&obj, gTrue)->isInt()) { - goto err1; + goto err2; } obj.free(); if (!parser->getObj(&obj, gTrue)->isCmd("obj")) { - goto err1; + goto err2; } obj.free(); if (!parser->getObj(&obj)->isStream()) { - goto err1; + goto err2; } more = readXRefStream(obj.getStream(), pos); obj.free(); + delete parser; } else { goto err1; } - delete parser; return more; - err1: + err2: obj.free(); delete parser; + err1: ok = gFalse; return gFalse; } -GBool XRef::readXRefTable(Parser *parser, Guint *pos) { +GBool XRef::readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet) { XRefEntry entry; - GBool more; + Parser *parser; Object obj, obj2; - Guint pos2; - int first, n, newSize, i; + char buf[6]; + GFileOffset off, pos2; + GBool more; + int first, n, newSize, gen, i, c; + + if (posSet->check(*pos)) { + error(errSyntaxWarning, -1, "Infinite loop in xref table"); + return gFalse; + } + posSet->add(*pos); + + str->setPos(start + *pos + offset); while (1) { - parser->getObj(&obj, gTrue); - if (obj.isCmd("trailer")) { - obj.free(); + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + if (c == 't') { + if (str->getBlock(buf, 6) != 6 || memcmp(buf, "railer", 6)) { + goto err1; + } break; } - if (!obj.isInt()) { + if (c < '0' || c > '9') { goto err1; } - first = obj.getInt(); - obj.free(); - if (!parser->getObj(&obj, gTrue)->isInt()) { + first = 0; + do { + first = (first * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { goto err1; } - n = obj.getInt(); - obj.free(); - if (first < 0 || n < 0 || first + n < 0) { + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + n = 0; + do { + n = (n * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { + goto err1; + } + if (first < 0 || n < 0 || first > INT_MAX - n) { goto err1; } if (first + n > size) { @@ -395,32 +527,51 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos) { } entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; } for (i = first; i < first + n; ++i) { - if (!parser->getObj(&obj, gTrue)->isInt()) { + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + off = 0; + do { + off = (off * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { goto err1; } - entry.offset = (Guint)obj.getInt(); - obj.free(); - if (!parser->getObj(&obj, gTrue)->isInt()) { + entry.offset = off; + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + gen = 0; + do { + gen = (gen * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { goto err1; } - entry.gen = obj.getInt(); - obj.free(); - parser->getObj(&obj, gTrue); - if (obj.isCmd("n")) { + entry.gen = gen; + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + if (c == 'n') { entry.type = xrefEntryUncompressed; - } else if (obj.isCmd("f")) { + } else if (c == 'f') { entry.type = xrefEntryFree; } else { goto err1; } - obj.free(); - if (entries[i].offset == 0xffffffff) { + c = str->getChar(); + if (!Lexer::isSpace(c)) { + goto err1; + } + if (entries[i].offset == (GFileOffset)-1) { entries[i] = entry; // PDF files of patents from the IBM Intellectual Property // Network have a bug: the xref table claims to start at 1 @@ -430,7 +581,7 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos) { entries[1].type == xrefEntryFree) { i = first = 0; entries[0] = entries[1]; - entries[1].offset = 0xffffffff; + entries[1].offset = (GFileOffset)-1; } if (i > last) { last = i; @@ -440,32 +591,29 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos) { } // read the trailer dictionary - if (!parser->getObj(&obj)->isDict()) { + obj.initNull(); + parser = new Parser(NULL, + new Lexer(NULL, + str->makeSubStream(str->getPos(), gFalse, 0, &obj)), + gTrue); + parser->getObj(&obj); + delete parser; + if (!obj.isDict()) { + obj.free(); goto err1; } // get the 'Prev' pointer + //~ this can be a 64-bit int (?) obj.getDict()->lookupNF("Prev", &obj2); if (obj2.isInt()) { - pos2 = (Guint)obj2.getInt(); - if (pos2 != *pos) { - *pos = pos2; - more = gTrue; - } else { - error(errSyntaxWarning, -1, "Infinite loop in xref table"); - more = gFalse; - } + *pos = (GFileOffset)(Guint)obj2.getInt(); + more = gTrue; } else if (obj2.isRef()) { // certain buggy PDF generators generate "/Prev NNN 0 R" instead // of "/Prev NNN" - pos2 = (Guint)obj2.getRefNum(); - if (pos2 != *pos) { - *pos = pos2; - more = gTrue; - } else { - error(errSyntaxWarning, -1, "Infinite loop in xref table"); - more = gFalse; - } + *pos = (GFileOffset)(Guint)obj2.getRefNum(); + more = gTrue; } else { more = gFalse; } @@ -477,9 +625,10 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos) { } // check for an 'XRefStm' key + //~ this can be a 64-bit int (?) if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) { - pos2 = (Guint)obj2.getInt(); - readXRef(&pos2); + pos2 = (GFileOffset)(Guint)obj2.getInt(); + readXRef(&pos2, posSet); if (!ok) { obj2.free(); goto err1; @@ -491,12 +640,11 @@ GBool XRef::readXRefTable(Parser *parser, Guint *pos) { return more; err1: - obj.free(); ok = gFalse; return gFalse; } -GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) { +GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos) { Dict *dict; int w[3]; GBool more; @@ -516,7 +664,7 @@ GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) { if (newSize > size) { entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; @@ -533,11 +681,13 @@ GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) { } w[i] = obj2.getInt(); obj2.free(); - if (w[i] < 0 || w[i] > 4) { - goto err1; - } } obj.free(); + if (w[0] < 0 || w[0] > 4 || + w[1] < 0 || w[1] > (int)sizeof(GFileOffset) || + w[2] < 0 || w[2] > 4) { + goto err0; + } xrefStr->reset(); dict->lookupNF("Index", &idx); @@ -569,9 +719,10 @@ GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) { } idx.free(); + //~ this can be a 64-bit int (?) dict->lookupNF("Prev", &obj); if (obj.isInt()) { - *pos = (Guint)obj.getInt(); + *pos = (GFileOffset)(Guint)obj.getInt(); more = gTrue; } else { more = gFalse; @@ -591,7 +742,7 @@ GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) { } GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { - Guint offset; + GFileOffset offset; int type, gen, c, newSize, i, j; if (first + n < 0) { @@ -606,7 +757,7 @@ GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { } entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; @@ -634,7 +785,7 @@ GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { } gen = (gen << 8) + c; } - if (entries[i].offset == 0xffffffff) { + if (entries[i].offset == (GFileOffset)-1) { switch (type) { case 0: entries[i].offset = offset; @@ -668,7 +819,7 @@ GBool XRef::constructXRef() { Parser *parser; Object newTrailerDict, obj; char buf[256]; - Guint pos; + GFileOffset pos; int num, gen; int newSize; int streamEndsSize; @@ -748,7 +899,7 @@ GBool XRef::constructXRef() { entries = (XRefEntry *) greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; @@ -771,15 +922,16 @@ GBool XRef::constructXRef() { } else if (!strncmp(p, "endstream", 9)) { if (streamEndsLen == streamEndsSize) { streamEndsSize += 64; - streamEnds = (Guint *)greallocn(streamEnds, - streamEndsSize, sizeof(Guint)); + streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize, + sizeof(GFileOffset)); } streamEnds[streamEndsLen++] = pos; } } - if (gotRoot) + if (gotRoot) { return gTrue; + } error(errSyntaxError, -1, "Couldn't find trailer dictionary"); return gFalse; @@ -824,13 +976,31 @@ GBool XRef::okToAddNotes(GBool ignoreOwnerPW) { Object *XRef::fetch(int num, int gen, Object *obj, int recursion) { XRefEntry *e; Parser *parser; + ObjectStream *objStr; Object obj1, obj2, obj3; + XRefCacheEntry tmp; + int i, j; // check for bogus ref - this can happen in corrupted PDF files if (num < 0 || num >= size) { goto err; } + // check the cache + if (cache[0].num == num && cache[0].gen == gen) { + return cache[0].obj.copy(obj); + } + for (i = 1; i < xrefCacheSize; ++i) { + if (cache[i].num == num && cache[i].gen == gen) { + tmp = cache[i]; + for (j = i; j > 0; --j) { + cache[j] = cache[j - 1]; + } + cache[0] = tmp; + return cache[0].obj.copy(obj); + } + } + e = &entries[num]; switch (e->type) { @@ -869,21 +1039,13 @@ Object *XRef::fetch(int num, int gen, Object *obj, int recursion) { goto err; } #endif - if (e->offset >= (Guint)size || + if (e->offset >= (GFileOffset)size || entries[e->offset].type != xrefEntryUncompressed) { error(errSyntaxError, -1, "Invalid object stream"); goto err; } - if (!objStr || objStr->getObjStrNum() != (int)e->offset) { - if (objStr) { - delete objStr; - } - objStr = new ObjectStream(this, e->offset); - if (!objStr->isOk()) { - delete objStr; - objStr = NULL; - goto err; - } + if (!(objStr = getObjectStream((int)e->offset))) { + goto err; } objStr->getObject(e->gen, num, obj); break; @@ -892,12 +1054,61 @@ Object *XRef::fetch(int num, int gen, Object *obj, int recursion) { goto err; } + // put the new object in the cache, throwing away the oldest object + // currently in the cache + if (cache[xrefCacheSize - 1].num >= 0) { + cache[xrefCacheSize - 1].obj.free(); + } + for (i = xrefCacheSize - 1; i > 0; --i) { + cache[i] = cache[i - 1]; + } + cache[0].num = num; + cache[0].gen = gen; + obj->copy(&cache[0].obj); + return obj; err: return obj->initNull(); } +ObjectStream *XRef::getObjectStream(int objStrNum) { + ObjectStream *objStr; + int i, j; + + // check the MRU entry in the cache + if (objStrs[0] && objStrs[0]->getObjStrNum() == objStrNum) { + return objStrs[0]; + } + + // check the rest of the cache + for (i = 1; i < objStrCacheSize; ++i) { + if (objStrs[i] && objStrs[i]->getObjStrNum() == objStrNum) { + objStr = objStrs[i]; + for (j = i; j > 0; --j) { + objStrs[j] = objStrs[j - 1]; + } + objStrs[0] = objStr; + return objStr; + } + } + + // load a new ObjectStream + objStr = new ObjectStream(this, objStrNum); + if (!objStr->isOk()) { + delete objStr; + return NULL; + } + if (objStrs[objStrCacheSize - 1]) { + delete objStrs[objStrCacheSize - 1]; + } + for (j = objStrCacheSize - 1; j > 0; --j) { + objStrs[j] = objStrs[j - 1]; + } + objStrs[0] = objStr; + return objStr; +} + Object *XRef::getDocInfo(Object *obj) { return trailerDict.dictLookup("Info", obj); } @@ -907,7 +1118,7 @@ Object *XRef::getDocInfoNF(Object *obj) { return trailerDict.dictLookupNF("Info", obj); } -GBool XRef::getStreamEnd(Guint streamStart, Guint *streamEnd) { +GBool XRef::getStreamEnd(GFileOffset streamStart, GFileOffset *streamEnd) { int a, b, m; if (streamEndsLen == 0 || @@ -930,14 +1141,14 @@ GBool XRef::getStreamEnd(Guint streamStart, Guint *streamEnd) { return gTrue; } -Guint XRef::strToUnsigned(char *s) { - Guint x, d; +GFileOffset XRef::strToFileOffset(char *s) { + GFileOffset x, d; char *p; x = 0; for (p = s; *p && isdigit(*p & 0xff); ++p) { d = *p - '0'; - if (x > (UINT_MAX - d) / 10) { + if (x > (GFILEOFFSET_MAX - d) / 10) { break; } x = 10 * x + d; diff --git a/xpdf/XRef.h b/xpdf/XRef.h index f2c4ac3..0b74dcb 100644 --- a/xpdf/XRef.h +++ b/xpdf/XRef.h @@ -16,12 +16,14 @@ #endif #include "gtypes.h" +#include "gfile.h" #include "Object.h" class Dict; class Stream; class Parser; class ObjectStream; +class XRefPosSet; //------------------------------------------------------------------------ // XRef @@ -34,11 +36,21 @@ enum XRefEntryType { }; struct XRefEntry { - Guint offset; + GFileOffset offset; int gen; XRefEntryType type; }; +struct XRefCacheEntry { + int num; + int gen; + Object obj; +}; + +#define xrefCacheSize 16 + +#define objStrCacheSize 4 + class XRef { public: @@ -83,7 +95,7 @@ public: int getNumObjects() { return last + 1; } // Return the offset of the last xref table. - Guint getLastXRefPos() { return lastXRefPos; } + GFileOffset getLastXRefPos() { return lastXRefPos; } // Return the catalog object reference. int getRootNum() { return rootNum; } @@ -91,7 +103,7 @@ public: // Get end position for a stream in a damaged file. // Returns false if unknown or file is not damaged. - GBool getStreamEnd(Guint streamStart, Guint *streamEnd); + GBool getStreamEnd(GFileOffset streamStart, GFileOffset *streamEnd); // Direct access. int getSize() { return size; } @@ -101,7 +113,7 @@ public: private: BaseStream *str; // input stream - Guint start; // offset in file (to allow for garbage + GFileOffset start; // offset in file (to allow for garbage // at beginning of file) XRefEntry *entries; // xref entries int size; // size of <entries> array @@ -110,11 +122,12 @@ private: GBool ok; // true if xref table is valid int errCode; // error code (if <ok> is false) Object trailerDict; // trailer dictionary - Guint lastXRefPos; // offset of last xref table - Guint *streamEnds; // 'endstream' positions - only used in + GFileOffset lastXRefPos; // offset of last xref table + GFileOffset *streamEnds; // 'endstream' positions - only used in // damaged files int streamEndsLen; // number of valid entries in streamEnds - ObjectStream *objStr; // cached object stream + ObjectStream * // cached object streams + objStrs[objStrCacheSize]; GBool encrypted; // true if file is encrypted int permFlags; // permission bits GBool ownerPasswordOk; // true if owner password is correct @@ -122,14 +135,17 @@ private: int keyLength; // length of key, in bytes int encVersion; // encryption version CryptAlgorithm encAlgorithm; // encryption algorithm + XRefCacheEntry // cache of recently accessed objects + cache[xrefCacheSize]; - Guint getStartXref(); - GBool readXRef(Guint *pos); - GBool readXRefTable(Parser *parser, Guint *pos); + GFileOffset getStartXref(); + GBool readXRef(GFileOffset *pos, XRefPosSet *posSet); + GBool readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet); GBool readXRefStreamSection(Stream *xrefStr, int *w, int first, int n); - GBool readXRefStream(Stream *xrefStr, Guint *pos); + GBool readXRefStream(Stream *xrefStr, GFileOffset *pos); GBool constructXRef(); - Guint strToUnsigned(char *s); + ObjectStream *getObjectStream(int objStrNum); + GFileOffset strToFileOffset(char *s); }; #endif diff --git a/xpdf/XpdfPluginAPI.cc b/xpdf/XpdfPluginAPI.cc index 4c51537..d513bec 100644 --- a/xpdf/XpdfPluginAPI.cc +++ b/xpdf/XpdfPluginAPI.cc @@ -14,7 +14,7 @@ #include "GlobalParams.h" #include "Object.h" #include "PDFDoc.h" -#ifdef WIN32 +#ifdef _WIN32 #include "WinPDFCore.h" #else #include "XPDFCore.h" diff --git a/xpdf/Zoox.cc b/xpdf/Zoox.cc new file mode 100644 index 0000000..9524267 --- /dev/null +++ b/xpdf/Zoox.cc @@ -0,0 +1,839 @@ +//======================================================================== +// +// Zoox.cc +// +//======================================================================== + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "gmem.h" +#include "GString.h" +#include "GList.h" +#include "GHash.h" +#include "Zoox.h" + +//~ all of this code assumes the encoding is UTF-8 or ASCII or something +//~ similar (ISO-8859-*) + +//------------------------------------------------------------------------ + +static char nameStartChar[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, // 30 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 70 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // a0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // b0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // c0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // d0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // e0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // f0 +}; + +static char nameChar[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, // 20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 30 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 70 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // a0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // b0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // c0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // d0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // e0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // f0 +}; + +//------------------------------------------------------------------------ + +ZxNode::ZxNode() { + next = NULL; + parent = NULL; + firstChild = lastChild = NULL; +} + +ZxNode::~ZxNode() { + ZxNode *child; + + while (firstChild) { + child = firstChild; + firstChild = firstChild->next; + delete child; + } +} + +ZxElement *ZxNode::findFirstElement(const char *type) { + ZxNode *child; + ZxElement *result; + + if (isElement(type)) { + return (ZxElement *)this; + } + for (child = firstChild; child; child = child->next) { + if ((result = child->findFirstElement(type))) { + return result; + } + } + return NULL; +} + +ZxElement *ZxNode::findFirstChildElement(const char *type) { + ZxNode *child; + + for (child = firstChild; child; child = child->next) { + if (child->isElement(type)) { + return (ZxElement *)child; + } + } + return NULL; +} + +GList *ZxNode::findAllElements(const char *type) { + GList *results; + + results = new GList(); + findAllElements(type, results); + return results; +} + +void ZxNode::findAllElements(const char *type, GList *results) { + ZxNode *child; + + if (isElement(type)) { + results->append(this); + } + for (child = firstChild; child; child = child->next) { + child->findAllElements(type, results); + } +} + +GList *ZxNode::findAllChildElements(const char *type) { + GList *results; + ZxNode *child; + + results = new GList(); + for (child = firstChild; child; child = child->next) { + if (child->isElement(type)) { + results->append(child); + } + } + return results; +} + +void ZxNode::addChild(ZxNode *child) { + if (lastChild) { + lastChild->next = child; + lastChild = child; + } else { + firstChild = lastChild = child; + } + child->parent = this; + child->next = NULL; +} + +//------------------------------------------------------------------------ + +ZxDoc::ZxDoc() { + xmlDecl = NULL; + docTypeDecl = NULL; + root = NULL; +} + +ZxDoc *ZxDoc::loadMem(const char *data, Guint dataLen) { + ZxDoc *doc; + + doc = new ZxDoc(); + if (!doc->parse(data, dataLen)) { + delete doc; + return NULL; + } + return doc; +} + +ZxDoc *ZxDoc::loadFile(const char *fileName) { + ZxDoc *doc; + FILE *f; + char *data; + Guint dataLen; + + if (!(f = fopen(fileName, "rb"))) { + return NULL; + } + fseek(f, 0, SEEK_END); + dataLen = (Guint)ftell(f); + if (!dataLen) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + data = (char *)gmalloc(dataLen); + if (fread(data, 1, dataLen, f) != dataLen) { + fclose(f); + gfree(data); + return NULL; + } + fclose(f); + doc = loadMem(data, dataLen); + gfree(data); + return doc; +} + +ZxDoc::~ZxDoc() { +} + +void ZxDoc::addChild(ZxNode *node) { + if (node->isXMLDecl() && !xmlDecl) { + xmlDecl = (ZxXMLDecl *)node; + } else if (node->isDocTypeDecl() && !docTypeDecl) { + docTypeDecl = (ZxDocTypeDecl *)node; + } else if (node->isElement() && !root) { + root = (ZxElement *)node; + } + ZxNode::addChild(node); +} + +bool ZxDoc::parse(const char *data, Guint dataLen) { + parsePtr = data; + parseEnd = data + dataLen; + + parseSpace(); + parseXMLDecl(this); + parseMisc(this); + parseDocTypeDecl(this); + parseMisc(this); + if (match("<")) { + parseElement(this); + } + parseMisc(this); + return root != NULL; +} + +void ZxDoc::parseXMLDecl(ZxNode *par) { + GString *version, *encoding, *s; + bool standalone; + + if (!match("<?xml")) { + return; + } + parsePtr += 5; + parseSpace(); + + // version + version = NULL; + if (match("version")) { + parsePtr += 7; + parseSpace(); + if (match("=")) { + ++parsePtr; + parseSpace(); + version = parseQuotedString(); + } + } + if (!version) { + version = new GString("1.0"); + } + parseSpace(); + + // encoding + encoding = NULL; + if (match("encoding")) { + parsePtr += 8; + parseSpace(); + if (match("=")) { + ++parsePtr; + parseSpace(); + encoding = parseQuotedString(); + } + } + parseSpace(); + + // standalone + standalone = false; + if (match("standalone")) { + parsePtr += 10; + parseSpace(); + if (match("=")) { + ++parsePtr; + parseSpace(); + s = parseQuotedString(); + standalone = !s->cmp("yes"); + delete s; + } + } + parseSpace(); + + if (match("?>")) { + parsePtr += 2; + } + + par->addChild(new ZxXMLDecl(version, encoding, standalone)); +} + +//~ this just skips everything after the name +void ZxDoc::parseDocTypeDecl(ZxNode *par) { + GString *name; + int state; + char c, quote; + + if (!match("<!DOCTYPE")) { + return; + } + parsePtr += 9; + parseSpace(); + + name = parseName(); + parseSpace(); + + state = 0; + quote = '\0'; + while (parsePtr < parseEnd && state < 4) { + c = *parsePtr++; + switch (state) { + case 0: // not in square brackets; not in quotes + if (c == '>') { + state = 4; + } else if (c == '"' || c == '\'') { + state = 1; + } else if (c == '[') { + state = 2; + } + break; + case 1: // not in square brackets; in quotes + if (c == quote) { + state = 0; + } + break; + case 2: // in square brackets; not in quotes + if (c == ']') { + state = 0; + } else if (c == '"' || c == '\'') { + state = 3; + } + break; + case 3: // in square brackets; in quotes + if (c == quote) { + state = 2; + } + break; + } + } + + par->addChild(new ZxDocTypeDecl(name)); +} + +// assumes match("<") +void ZxDoc::parseElement(ZxNode *par) { + GString *type; + ZxElement *elem; + ZxAttr *attr; + + ++parsePtr; + type = parseName(); + elem = new ZxElement(type); + parseSpace(); + while ((attr = parseAttr())) { + elem->addAttr(attr); + parseSpace(); + } + if (match("/>")) { + parsePtr += 2; + } else if (match(">")) { + ++parsePtr; + parseContent(elem); + } + par->addChild(elem); +} + +ZxAttr *ZxDoc::parseAttr() { + GString *name, *value; + const char *start; + char quote, c; + int x, n; + + name = parseName(); + parseSpace(); + if (!match("=")) { + delete name; + return NULL; + } + ++parsePtr; + parseSpace(); + if (!(parsePtr < parseEnd && (*parsePtr == '"' || *parsePtr == '\''))) { + delete name; + return NULL; + } + quote = *parsePtr++; + value = new GString(); + while (parsePtr < parseEnd && *parsePtr != quote) { + if (*parsePtr == '&') { + ++parsePtr; + if (parsePtr < parseEnd && *parsePtr == '#') { + ++parsePtr; + if (parsePtr < parseEnd && *parsePtr == 'x') { + ++parsePtr; + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = (x << 4) + (c - '0'); + } else if (c >= 'a' && c <= 'f') { + x = (x << 4) + (10 + c - 'a'); + } else if (c >= 'A' && c <= 'F') { + x = (x << 4) + (10 + c - 'A'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(value, x); + } else { + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = x * 10 + (c - '0'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(value, x); + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != ';' && + *parsePtr != quote && *parsePtr != '&'; + ++parsePtr) ; + n = (int)(parsePtr - start); + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + if (n == 2 && !strncmp(start, "lt", 2)) { + value->append('<'); + } else if (n == 2 && !strncmp(start, "gt", 2)) { + value->append('>'); + } else if (n == 3 && !strncmp(start, "amp", 3)) { + value->append('&'); + } else if (n == 4 && !strncmp(start, "apos", 4)) { + value->append('\''); + } else if (n == 4 && !strncmp(start, "quot", 4)) { + value->append('"'); + } else { + value->append(start - 1, (int)(parsePtr - start) + 1); + } + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != quote && *parsePtr != '&'; + ++parsePtr) ; + value->append(start, (int)(parsePtr - start)); + } + } + if (parsePtr < parseEnd && *parsePtr == quote) { + ++parsePtr; + } + return new ZxAttr(name, value); +} + +// this consumes the end tag +void ZxDoc::parseContent(ZxElement *par) { + GString *endType; + + endType = (new GString("</"))->append(par->getType()); + + while (parsePtr < parseEnd) { + if (match(endType->getCString())) { + parsePtr += endType->getLength(); + parseSpace(); + if (match(">")) { + ++parsePtr; + } + break; + } else if (match("<?")) { + parsePI(par); + } else if (match("<![CDATA[")) { + parseCDSect(par); + } else if (match("<!--")) { + parseComment(par); + } else if (match("<")) { + parseElement(par); + } else { + parseCharData(par); + } + } + + delete endType; +} + +void ZxDoc::parseCharData(ZxElement *par) { + GString *data; + const char *start; + char c; + int x, n; + + data = new GString(); + while (parsePtr < parseEnd && *parsePtr != '<') { + if (*parsePtr == '&') { + ++parsePtr; + if (parsePtr < parseEnd && *parsePtr == '#') { + ++parsePtr; + if (parsePtr < parseEnd && *parsePtr == 'x') { + ++parsePtr; + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = (x << 4) + (c - '0'); + } else if (c >= 'a' && c <= 'f') { + x = (x << 4) + (10 + c - 'a'); + } else if (c >= 'A' && c <= 'F') { + x = (x << 4) + (10 + c - 'A'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(data, x); + } else { + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = x * 10 + (c - '0'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(data, x); + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != ';' && + *parsePtr != '<' && *parsePtr != '&'; + ++parsePtr) ; + n = (int)(parsePtr - start); + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + if (n == 2 && !strncmp(start, "lt", 2)) { + data->append('<'); + } else if (n == 2 && !strncmp(start, "gt", 2)) { + data->append('>'); + } else if (n == 3 && !strncmp(start, "amp", 3)) { + data->append('&'); + } else if (n == 4 && !strncmp(start, "apos", 4)) { + data->append('\''); + } else if (n == 4 && !strncmp(start, "quot", 4)) { + data->append('"'); + } else { + data->append(start - 1, (int)(parsePtr - start) + 1); + } + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != '<' && *parsePtr != '&'; + ++parsePtr) ; + data->append(start, (int)(parsePtr - start)); + } + } + par->addChild(new ZxCharData(data, true)); +} + +void ZxDoc::appendUTF8(GString *s, int c) { + if (c <= 0x7f) { + s->append((char)c); + } else if (c <= 0x7ff) { + s->append((char)(0xc0 + (c >> 6))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0xffff) { + s->append((char)(0xe0 + (c >> 12))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0x1fffff) { + s->append((char)(0xf0 + (c >> 18))); + s->append((char)(0x80 + ((c >> 12) & 0x3f))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0x3ffffff) { + s->append((char)(0xf8 + (c >> 24))); + s->append((char)(0x80 + ((c >> 18) & 0x3f))); + s->append((char)(0x80 + ((c >> 12) & 0x3f))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0x7fffffff) { + s->append((char)(0xfc + (c >> 30))); + s->append((char)(0x80 + ((c >> 24) & 0x3f))); + s->append((char)(0x80 + ((c >> 18) & 0x3f))); + s->append((char)(0x80 + ((c >> 12) & 0x3f))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } +} + +// assumes match("<![CDATA[") +void ZxDoc::parseCDSect(ZxNode *par) { + const char *start; + + parsePtr += 9; + start = parsePtr; + while (parsePtr < parseEnd - 3) { + if (!strncmp(parsePtr, "]]>", 3)) { + par->addChild(new ZxCharData(new GString(start, (int)(parsePtr - start)), + false)); + parsePtr += 3; + return; + } + ++parsePtr; + } + parsePtr = parseEnd; + par->addChild(new ZxCharData(new GString(start, (int)(parsePtr - start)), + false)); +} + +void ZxDoc::parseMisc(ZxNode *par) { + while (1) { + if (match("<!--")) { + parseComment(par); + } else if (match("<?")) { + parsePI(par); + } else if (parsePtr < parseEnd && (*parsePtr == '\x20' || + *parsePtr == '\x09' || + *parsePtr == '\x0d' || + *parsePtr == '\x0a')) { + ++parsePtr; + } else { + break; + } + } +} + +// assumes match("<!--") +void ZxDoc::parseComment(ZxNode *par) { + const char *start; + + parsePtr += 4; + start = parsePtr; + while (parsePtr <= parseEnd - 3) { + if (!strncmp(parsePtr, "-->", 3)) { + par->addChild(new ZxComment(new GString(start, (int)(parsePtr - start)))); + parsePtr += 3; + return; + } + ++parsePtr; + } + parsePtr = parseEnd; +} + +// assumes match("<?") +void ZxDoc::parsePI(ZxNode *par) { + GString *target; + const char *start; + + parsePtr += 2; + target = parseName(); + parseSpace(); + start = parsePtr; + while (parsePtr <= parseEnd - 2) { + if (!strncmp(parsePtr, "?>", 2)) { + par->addChild(new ZxPI(target, new GString(start, + (int)(parsePtr - start)))); + parsePtr += 2; + return; + } + ++parsePtr; + } + parsePtr = parseEnd; + par->addChild(new ZxPI(target, new GString(start, (int)(parsePtr - start)))); +} + +//~ this accepts all chars >= 0x80 +//~ this doesn't check for properly-formed UTF-8 +GString *ZxDoc::parseName() { + GString *name; + + name = new GString(); + if (parsePtr < parseEnd && nameStartChar[*parsePtr & 0xff]) { + name->append(*parsePtr++); + while (parsePtr < parseEnd && nameChar[*parsePtr & 0xff]) { + name->append(*parsePtr++); + } + } + return name; +} + +GString *ZxDoc::parseQuotedString() { + GString *s; + const char *start; + char quote; + + if (parsePtr < parseEnd && (*parsePtr == '"' || *parsePtr == '\'')) { + quote = *parsePtr++; + start = parsePtr; + while (parsePtr < parseEnd && *parsePtr != quote) { + ++parsePtr; + } + s = new GString(start, (int)(parsePtr - start)); + if (parsePtr < parseEnd && *parsePtr == quote) { + ++parsePtr; + } + } else { + s = new GString(); + } + return s; +} + +void ZxDoc::parseSpace() { + while (parsePtr < parseEnd && (*parsePtr == '\x20' || + *parsePtr == '\x09' || + *parsePtr == '\x0d' || + *parsePtr == '\x0a')) { + ++parsePtr; + } +} + +bool ZxDoc::match(const char *s) { + int n; + + n = (int)strlen(s); + return parseEnd - parsePtr >= n && !strncmp(parsePtr, s, n); +} + +//------------------------------------------------------------------------ + +ZxXMLDecl::ZxXMLDecl(GString *versionA, GString *encodingA, bool standaloneA) { + version = versionA; + encoding = encodingA; + standalone = standaloneA; +} + +ZxXMLDecl::~ZxXMLDecl() { + delete version; + if (encoding) { + delete encoding; + } +} + +//------------------------------------------------------------------------ + +ZxDocTypeDecl::ZxDocTypeDecl(GString *nameA) { + name = nameA; +} + +ZxDocTypeDecl::~ZxDocTypeDecl() { + delete name; +} + +//------------------------------------------------------------------------ + +ZxComment::ZxComment(GString *textA) { + text = textA; +} + +ZxComment::~ZxComment() { + delete text; +} + +//------------------------------------------------------------------------ + +ZxPI::ZxPI(GString *targetA, GString *textA) { + target = targetA; + text = textA; +} + +ZxPI::~ZxPI() { + delete target; + delete text; +} + +//------------------------------------------------------------------------ + +ZxElement::ZxElement(GString *typeA) { + type = typeA; + attrs = new GHash(); + firstAttr = lastAttr = NULL; +} + +ZxElement::~ZxElement() { + delete type; + deleteGHash(attrs, ZxAttr); +} + +bool ZxElement::isElement(const char *typeA) { + return !type->cmp(typeA); +} + +ZxAttr *ZxElement::findAttr(const char *attrName) { + return (ZxAttr *)attrs->lookup(attrName); +} + +void ZxElement::addAttr(ZxAttr *attr) { + attrs->add(attr->getName(), attr); + if (lastAttr) { + lastAttr->next = attr; + lastAttr= attr; + } else { + firstAttr = lastAttr = attr; + } + attr->parent = this; + attr->next = NULL; +} + +//------------------------------------------------------------------------ + +ZxAttr::ZxAttr(GString *nameA, GString *valueA) { + name = nameA; + value = valueA; + parent = NULL; + next = NULL; +} + +ZxAttr::~ZxAttr() { + delete name; + delete value; +} + +//------------------------------------------------------------------------ + +ZxCharData::ZxCharData(GString *dataA, bool parsedA) { + data = dataA; + parsed = parsedA; +} + +ZxCharData::~ZxCharData() { + delete data; +} diff --git a/xpdf/Zoox.h b/xpdf/Zoox.h new file mode 100644 index 0000000..091f72e --- /dev/null +++ b/xpdf/Zoox.h @@ -0,0 +1,241 @@ +//======================================================================== +// +// Zoox.h +// +//======================================================================== + +#ifndef ZOOX_H +#define ZOOX_H + +#include <aconf.h> + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "gtypes.h" + +class GString; +class GList; +class GHash; + +class ZxAttr; +class ZxDocTypeDecl; +class ZxElement; +class ZxXMLDecl; + +//------------------------------------------------------------------------ + +class ZxNode { +public: + + ZxNode(); + virtual ~ZxNode(); + + virtual bool isDoc() { return false; } + virtual bool isXMLDecl() { return false; } + virtual bool isDocTypeDecl() { return false; } + virtual bool isComment() { return false; } + virtual bool isPI() { return false; } + virtual bool isElement() { return false; } + virtual bool isElement(const char *type) { return false; } + virtual bool isCharData() { return false; } + virtual ZxNode *getFirstChild() { return firstChild; } + virtual ZxNode *getNextChild() { return next; } + ZxElement *findFirstElement(const char *type); + ZxElement *findFirstChildElement(const char *type); + GList *findAllElements(const char *type); + GList *findAllChildElements(const char *type); + virtual void addChild(ZxNode *child); + +protected: + + void findAllElements(const char *type, GList *results); + + ZxNode *next; + ZxNode *parent; + ZxNode *firstChild, + *lastChild; +}; + +//------------------------------------------------------------------------ + +class ZxDoc: public ZxNode { +public: + + ZxDoc(); + + // Parse from memory. Returns NULL on error. + static ZxDoc *loadMem(const char *data, Guint dataLen); + + // Parse from disk. Returns NULL on error. + static ZxDoc *loadFile(const char *fileName); + + virtual ~ZxDoc(); + + virtual bool isDoc() { return true; } + ZxXMLDecl *getXMLDecl() { return xmlDecl; } + ZxDocTypeDecl *getDocTypeDecl() { return docTypeDecl; } + ZxElement *getRoot() { return root; } + virtual void addChild(ZxNode *node); + +private: + + bool parse(const char *data, Guint dataLen); + void parseXMLDecl(ZxNode *par); + void parseDocTypeDecl(ZxNode *par); + void parseElement(ZxNode *par); + ZxAttr *parseAttr(); + void parseContent(ZxElement *par); + void parseCharData(ZxElement *par); + void appendUTF8(GString *s, int c); + void parseCDSect(ZxNode *par); + void parseMisc(ZxNode *par); + void parseComment(ZxNode *par); + void parsePI(ZxNode *par); + GString *parseName(); + GString *parseQuotedString(); + void parseSpace(); + bool match(const char *s); + + ZxXMLDecl *xmlDecl; // may be NULL + ZxDocTypeDecl *docTypeDecl; // may be NULL + ZxElement *root; // may be NULL + + const char *parsePtr; + const char *parseEnd; +}; + +//------------------------------------------------------------------------ + +class ZxXMLDecl: public ZxNode { +public: + + ZxXMLDecl(GString *versionA, GString *encodingA, bool standaloneA); + virtual ~ZxXMLDecl(); + + virtual bool isXMLDecl() { return true; } + GString *getVersion() { return version; } + GString *getEncoding() { return encoding; } + bool getStandalone() { return standalone; } + +private: + + GString *version; + GString *encoding; // may be NULL + bool standalone; +}; + +//------------------------------------------------------------------------ + +class ZxDocTypeDecl: public ZxNode { +public: + + ZxDocTypeDecl(GString *nameA); + virtual ~ZxDocTypeDecl(); + + virtual bool isDocTypeDecl() { return true; } + GString *getName() { return name; } + +private: + + GString *name; +}; + +//------------------------------------------------------------------------ + +class ZxComment: public ZxNode { +public: + + ZxComment(GString *textA); + virtual ~ZxComment(); + + virtual bool isComment() { return true; } + GString *getText() { return text; } + +private: + + GString *text; +}; + +//------------------------------------------------------------------------ + +class ZxPI: public ZxNode { +public: + + ZxPI(GString *targetA, GString *textA); + virtual ~ZxPI(); + + virtual bool isPI() { return true; } + GString *getTarget() { return target; } + GString *getText() { return text; } + +private: + + GString *target; + GString *text; +}; + +//------------------------------------------------------------------------ + +class ZxElement: public ZxNode { +public: + + ZxElement(GString *typeA); + virtual ~ZxElement(); + + virtual bool isElement() { return true; } + virtual bool isElement(const char *typeA); + GString *getType() { return type; } + ZxAttr *findAttr(const char *attrName); + ZxAttr *getFirstAttr() { return firstAttr; } + void addAttr(ZxAttr *attr); + +private: + + GString *type; + GHash *attrs; // [ZxAttr] + ZxAttr *firstAttr, *lastAttr; +}; + +//------------------------------------------------------------------------ + +class ZxAttr { +public: + + ZxAttr(GString *nameA, GString *valueA); + ~ZxAttr(); + + GString *getName() { return name; } + GString *getValue() { return value; } + ZxAttr *getNextAttr() { return next; } + +private: + + GString *name; + GString *value; + ZxElement *parent; + ZxAttr *next; + + friend class ZxElement; +}; + +//------------------------------------------------------------------------ + +class ZxCharData: public ZxNode { +public: + + ZxCharData(GString *dataA, bool parsedA); + virtual ~ZxCharData(); + + virtual bool isCharData() { return true; } + GString *getData() { return data; } + bool isParsed() { return parsed; } + +private: + + GString *data; // in UTF-8 format + bool parsed; +}; + +#endif diff --git a/xpdf/config.h b/xpdf/config.h index 998d345..c2b8779 100644 --- a/xpdf/config.h +++ b/xpdf/config.h @@ -2,7 +2,7 @@ // // config.h // -// Copyright 1996-2011 Glyph & Cog, LLC +// Copyright 1996-2014 Glyph & Cog, LLC // //======================================================================== @@ -14,13 +14,13 @@ //------------------------------------------------------------------------ // xpdf version -#define xpdfVersion "3.03" -#define xpdfVersionNum 3.03 +#define xpdfVersion "3.04" +#define xpdfVersionNum 3.04 #define xpdfMajorVersion 3 -#define xpdfMinorVersion 3 +#define xpdfMinorVersion 4 #define xpdfUpdateVersion 0 #define xpdfMajorVersionStr "3" -#define xpdfMinorVersionStr "3" +#define xpdfMinorVersionStr "4" #define xpdfUpdateVersionStr "0" // supported PDF version @@ -28,11 +28,11 @@ #define supportedPDFVersionNum 1.7 // copyright notice -#define xpdfCopyright "Copyright 1996-2011 Glyph & Cog, LLC" +#define xpdfCopyright "Copyright 1996-2014 Glyph & Cog, LLC" // Windows resource file stuff -#define winxpdfVersion "WinXpdf 3.03" -#define xpdfCopyrightAmp "Copyright 1996-2011 Glyph && Cog, LLC" +#define winxpdfVersion "WinXpdf 3.04" +#define xpdfCopyrightAmp "Copyright 1996-2014 Glyph && Cog, LLC" //------------------------------------------------------------------------ // paper size @@ -52,7 +52,7 @@ //------------------------------------------------------------------------ // user config file name, relative to the user's home directory -#if defined(VMS) || defined(WIN32) +#if defined(VMS) || defined(_WIN32) #define xpdfUserConfigFile "xpdfrc" #else #define xpdfUserConfigFile ".xpdfrc" @@ -83,7 +83,7 @@ #define pclose _pclose #endif -#if defined(VMS) || defined(VMCMS) || defined(DOS) || defined(OS2) || defined(__EMX__) || defined(WIN32) || defined(__DJGPP__) || defined(MACOS) +#if defined(VMS) || defined(VMCMS) || defined(DOS) || defined(OS2) || defined(__EMX__) || defined(_WIN32) || defined(__DJGPP__) || defined(MACOS) #define POPEN_READ_MODE "rb" #else #define POPEN_READ_MODE "r" diff --git a/xpdf/pdfdetach.cc b/xpdf/pdfdetach.cc index 4be3a48..6d29ff3 100644 --- a/xpdf/pdfdetach.cc +++ b/xpdf/pdfdetach.cc @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) { } else if (saveAll) { for (i = 0; i < nFiles; ++i) { if (savePath[0]) { - n = strlen(savePath); + n = (int)strlen(savePath); if (n > (int)sizeof(path) - 2) { n = sizeof(path) - 2; } diff --git a/xpdf/pdffonts.cc b/xpdf/pdffonts.cc index 80403a1..e1a0b43 100644 --- a/xpdf/pdffonts.cc +++ b/xpdf/pdffonts.cc @@ -22,6 +22,7 @@ #include "Dict.h" #include "GfxFont.h" #include "Annot.h" +#include "Form.h" #include "PDFDoc.h" #include "config.h" @@ -41,11 +42,14 @@ static const char *fontTypeNames[] = { "CID TrueType (OT)" }; +static void scanFonts(Object *obj, PDFDoc *doc); static void scanFonts(Dict *resDict, PDFDoc *doc); static void scanFont(GfxFont *font, PDFDoc *doc); static int firstPage = 1; static int lastPage = 0; +static GBool showFontLoc = gFalse; +static GBool showFontLocPS = gFalse; static char ownerPassword[33] = "\001"; static char userPassword[33] = "\001"; static char cfgFileName[256] = ""; @@ -57,6 +61,10 @@ static ArgDesc argDesc[] = { "first page to examine"}, {"-l", argInt, &lastPage, 0, "last page to examine"}, + {"-loc", argFlag, &showFontLoc, 0, + "print extended info on font location"}, + {"-locPS", argFlag, &showFontLocPS, 0, + "print extended info on font location for PostScript conversion"}, {"-opw", argString, ownerPassword, sizeof(ownerPassword), "owner password (for encrypted files)"}, {"-upw", argString, userPassword, sizeof(userPassword), @@ -80,6 +88,10 @@ static Ref *fonts; static int fontsLen; static int fontsSize; +static Ref *seenObjs; +static int seenObjsLen; +static int seenObjsSize; + int main(int argc, char *argv[]) { PDFDoc *doc; GString *fileName; @@ -88,8 +100,9 @@ int main(int argc, char *argv[]) { Page *page; Dict *resDict; Annots *annots; + Form *form; Object obj1, obj2; - int pg, i; + int pg, i, j; int exitCode; exitCode = 99; @@ -108,6 +121,7 @@ int main(int argc, char *argv[]) { // read config file globalParams = new GlobalParams(cfgFileName); + globalParams->setupBaseFonts(NULL); // open PDF file if (ownerPassword[0] != '\001') { @@ -141,10 +155,17 @@ int main(int argc, char *argv[]) { } // scan the fonts - printf("name type emb sub uni object ID\n"); - printf("------------------------------------ ----------------- --- --- --- ---------\n"); + if (showFontLoc || showFontLocPS) { + printf("name type emb sub uni object ID location\n"); + printf("------------------------------------ ----------------- --- --- --- --------- --------\n"); + } else { + printf("name type emb sub uni object ID\n"); + printf("------------------------------------ ----------------- --- --- --- ---------\n"); + } fonts = NULL; fontsLen = fontsSize = 0; + seenObjs = NULL; + seenObjsLen = seenObjsSize = 0; for (pg = firstPage; pg <= lastPage; ++pg) { page = doc->getCatalog()->getPage(pg); if ((resDict = page->getResourceDict())) { @@ -154,21 +175,35 @@ int main(int argc, char *argv[]) { obj1.free(); for (i = 0; i < annots->getNumAnnots(); ++i) { if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) { - obj1.streamGetDict()->lookup("Resources", &obj2); - if (obj2.isDict()) { - scanFonts(obj2.getDict(), doc); - } + obj1.streamGetDict()->lookupNF("Resources", &obj2); + scanFonts(&obj2, doc); obj2.free(); } obj1.free(); } delete annots; } + if ((form = doc->getCatalog()->getForm())) { + for (i = 0; i < form->getNumFields(); ++i) { + form->getField(i)->getResources(&obj1); + if (obj1.isArray()) { + for (j = 0; j < obj1.arrayGetLength(); ++j) { + obj1.arrayGetNF(j, &obj2); + scanFonts(&obj2, doc); + obj2.free(); + } + } else if (obj1.isDict()) { + scanFonts(obj1.getDict(), doc); + } + obj1.free(); + } + } exitCode = 0; // clean up gfree(fonts); + gfree(seenObjs); err1: delete doc; delete globalParams; @@ -181,8 +216,37 @@ int main(int argc, char *argv[]) { return exitCode; } +static void scanFonts(Object *obj, PDFDoc *doc) { + Object obj2; + int i; + + if (obj->isRef()) { + for (i = 0; i < seenObjsLen; ++i) { + if (obj->getRefNum() == seenObjs[i].num && + obj->getRefGen() == seenObjs[i].gen) { + return; + } + } + if (seenObjsLen == seenObjsSize) { + if (seenObjsSize <= INT_MAX - 32) { + seenObjsSize += 32; + } else { + // let greallocn throw an exception + seenObjsSize = -1; + } + seenObjs = (Ref *)greallocn(seenObjs, seenObjsSize, sizeof(Ref)); + } + seenObjs[seenObjsLen++] = obj->getRef(); + } + if (obj->fetch(doc->getXRef(), &obj2)->isDict()) { + scanFonts(obj2.getDict(), doc); + } + obj2.free(); +} + static void scanFonts(Dict *resDict, PDFDoc *doc) { - Object obj1, obj2, xObjDict, xObj, resObj; + Object obj1, obj2, xObjDict, xObj; + Object patternDict, pattern, gsDict, gs, smask, smaskGroup, resObj; Ref r; GfxFontDict *gfxFontDict; GfxFont *font; @@ -211,23 +275,58 @@ static void scanFonts(Dict *resDict, PDFDoc *doc) { } obj1.free(); - // recursively scan any resource dictionaries in objects in this + // recursively scan any resource dictionaries in XObjects in this // resource dictionary resDict->lookup("XObject", &xObjDict); if (xObjDict.isDict()) { for (i = 0; i < xObjDict.dictGetLength(); ++i) { xObjDict.dictGetVal(i, &xObj); if (xObj.isStream()) { - xObj.streamGetDict()->lookup("Resources", &resObj); - if (resObj.isDict()) { - scanFonts(resObj.getDict(), doc); - } + xObj.streamGetDict()->lookupNF("Resources", &resObj); + scanFonts(&resObj, doc); resObj.free(); } xObj.free(); } } xObjDict.free(); + + // recursively scan any resource dictionaries in Patterns in this + // resource dictionary + resDict->lookup("Pattern", &patternDict); + if (patternDict.isDict()) { + for (i = 0; i < patternDict.dictGetLength(); ++i) { + patternDict.dictGetVal(i, &pattern); + if (pattern.isStream()) { + pattern.streamGetDict()->lookupNF("Resources", &resObj); + scanFonts(&resObj, doc); + resObj.free(); + } + pattern.free(); + } + } + patternDict.free(); + + // recursively scan any resource dictionaries in ExtGStates in this + // resource dictionary + resDict->lookup("ExtGState", &gsDict); + if (gsDict.isDict()) { + for (i = 0; i < gsDict.dictGetLength(); ++i) { + if (gsDict.dictGetVal(i, &gs)->isDict()) { + if (gs.dictLookup("SMask", &smask)->isDict()) { + if (smask.dictLookup("G", &smaskGroup)->isStream()) { + smaskGroup.streamGetDict()->lookupNF("Resources", &resObj); + scanFonts(&resObj, doc); + resObj.free(); + } + smaskGroup.free(); + } + smask.free(); + } + gs.free(); + } + } + gsDict.free(); } static void scanFont(GfxFont *font, PDFDoc *doc) { @@ -235,6 +334,7 @@ static void scanFont(GfxFont *font, PDFDoc *doc) { Object fontObj, toUnicodeObj; GString *name; GBool emb, subset, hasToUnicode; + GfxFontLoc *loc; int i; fontRef = *font->getID(); @@ -284,10 +384,38 @@ static void scanFont(GfxFont *font, PDFDoc *doc) { subset ? "yes" : "no", hasToUnicode ? "yes" : "no"); if (fontRef.gen >= 100000) { - printf(" [none]\n"); + printf(" [none]"); } else { - printf(" %6d %2d\n", fontRef.num, fontRef.gen); + printf(" %6d %2d", fontRef.num, fontRef.gen); + } + if (showFontLoc || showFontLocPS) { + if (font->getType() == fontType3) { + printf(" embedded"); + } else { + loc = font->locateFont(doc->getXRef(), showFontLocPS); + if (loc) { + if (loc->locType == gfxFontLocEmbedded) { + printf(" embedded"); + } else if (loc->locType == gfxFontLocExternal) { + if (loc->path) { + printf(" external: %s", loc->path->getCString()); + } else { + printf(" unavailable"); + } + } else if (loc->locType == gfxFontLocResident) { + if (loc->path) { + printf(" resident: %s", loc->path->getCString()); + } else { + printf(" unavailable"); + } + } + } else { + printf(" unknown"); + } + delete loc; + } } + printf("\n"); // add this font to the list if (fontsLen == fontsSize) { diff --git a/xpdf/pdfinfo.cc b/xpdf/pdfinfo.cc index ad482ac..9c0493d 100644 --- a/xpdf/pdfinfo.cc +++ b/xpdf/pdfinfo.cc @@ -2,7 +2,7 @@ // // pdfinfo.cc // -// Copyright 1998-2003 Glyph & Cog, LLC +// Copyright 1998-2013 Glyph & Cog, LLC // //======================================================================== @@ -16,6 +16,7 @@ #include "parseargs.h" #include "GString.h" #include "gmem.h" +#include "gfile.h" #include "GlobalParams.h" #include "Object.h" #include "Stream.h" @@ -27,7 +28,7 @@ #include "PDFDoc.h" #include "CharTypes.h" #include "UnicodeMap.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "Error.h" #include "config.h" @@ -239,6 +240,7 @@ int main(int argc, char *argv[]) { wISO /= sqrt(2.0); } } + printf(" (rotated %d degrees)", doc->getPageRotate(pg)); printf("\n"); } @@ -275,16 +277,8 @@ int main(int argc, char *argv[]) { f = fopen(fileName->getCString(), "rb"); #endif if (f) { -#if HAVE_FSEEKO - fseeko(f, 0, SEEK_END); - printf("File size: %u bytes\n", (Guint)ftello(f)); -#elif HAVE_FSEEK64 - fseek64(f, 0, SEEK_END); - printf("File size: %u bytes\n", (Guint)ftell64(f)); -#else - fseek(f, 0, SEEK_END); - printf("File size: %d bytes\n", (int)ftell(f)); -#endif + gfseek(f, 0, SEEK_END); + printf("File size: %u bytes\n", (Guint)gftell(f)); fclose(f); } @@ -322,36 +316,21 @@ int main(int argc, char *argv[]) { static void printInfoString(Dict *infoDict, const char *key, const char *text, UnicodeMap *uMap) { Object obj; - GString *s1; - GBool isUnicode; - Unicode u; + TextString *s; + Unicode *u; char buf[8]; int i, n; if (infoDict->lookup(key, &obj)->isString()) { fputs(text, stdout); - s1 = obj.getString(); - if ((s1->getChar(0) & 0xff) == 0xfe && - (s1->getChar(1) & 0xff) == 0xff) { - isUnicode = gTrue; - i = 2; - } else { - isUnicode = gFalse; - i = 0; - } - while (i < obj.getString()->getLength()) { - if (isUnicode) { - u = ((s1->getChar(i) & 0xff) << 8) | - (s1->getChar(i+1) & 0xff); - i += 2; - } else { - u = pdfDocEncoding[s1->getChar(i) & 0xff]; - ++i; - } - n = uMap->mapUnicode(u, buf, sizeof(buf)); + s = new TextString(obj.getString()); + u = s->getUnicode(); + for (i = 0; i < s->getLength(); ++i) { + n = uMap->mapUnicode(u[i], buf, sizeof(buf)); fwrite(buf, 1, n, stdout); } fputc('\n', stdout); + delete s; } obj.free(); } diff --git a/xpdf/pdftohtml.cc b/xpdf/pdftohtml.cc new file mode 100644 index 0000000..f1fe691 --- /dev/null +++ b/xpdf/pdftohtml.cc @@ -0,0 +1,246 @@ +//======================================================================== +// +// pdftohtml.cc +// +// Copyright 2005 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> +#include <stdio.h> +#include <stdlib.h> +#include "parseargs.h" +#include "gmem.h" +#include "gfile.h" +#include "GString.h" +#include "GlobalParams.h" +#include "PDFDoc.h" +#include "HTMLGen.h" +#include "Error.h" +#include "ErrorCodes.h" +#include "config.h" + +//------------------------------------------------------------------------ + +static GBool createIndex(char *htmlDir); + +//------------------------------------------------------------------------ + +static int firstPage = 1; +static int lastPage = 0; +static int resolution = 150; +static GBool skipInvisible = gFalse; +static char ownerPassword[33] = "\001"; +static char userPassword[33] = "\001"; +static GBool quiet = gFalse; +static char cfgFileName[256] = ""; +static GBool printVersion = gFalse; +static GBool printHelp = gFalse; + +static ArgDesc argDesc[] = { + {"-f", argInt, &firstPage, 0, + "first page to convert"}, + {"-l", argInt, &lastPage, 0, + "last page to convert"}, + {"-r", argInt, &resolution, 0, + "resolution, in DPI (default is 150)"}, + {"-skipinvisible", argFlag, &skipInvisible, 0, + "do not draw invisible text"}, + {"-opw", argString, ownerPassword, sizeof(ownerPassword), + "owner password (for encrypted files)"}, + {"-upw", argString, userPassword, sizeof(userPassword), + "user password (for encrypted files)"}, + {"-q", argFlag, &quiet, 0, + "don't print any messages or errors"}, + {"-cfg", argString, cfgFileName, sizeof(cfgFileName), + "configuration file to use in place of .xpdfrc"}, + {"-v", argFlag, &printVersion, 0, + "print copyright and version info"}, + {"-h", argFlag, &printHelp, 0, + "print usage information"}, + {"-help", argFlag, &printHelp, 0, + "print usage information"}, + {"--help", argFlag, &printHelp, 0, + "print usage information"}, + {"-?", argFlag, &printHelp, 0, + "print usage information"}, + {NULL} +}; + +//------------------------------------------------------------------------ + +static int writeToFile(void *file, const char *data, int size) { + return (int)fwrite(data, 1, size, (FILE *)file); +} + +int main(int argc, char *argv[]) { + PDFDoc *doc; + GString *fileName; + char *htmlDir; + GString *ownerPW, *userPW; + HTMLGen *htmlGen; + GString *htmlFileName, *pngFileName, *pngURL; + FILE *htmlFile, *pngFile; + int pg, err, exitCode; + GBool ok; + + exitCode = 99; + + // parse args + ok = parseArgs(argDesc, &argc, argv); + if (!ok || argc != 3 || printVersion || printHelp) { + fprintf(stderr, "pdftohtml version %s\n", xpdfVersion); + fprintf(stderr, "%s\n", xpdfCopyright); + if (!printVersion) { + printUsage("pdftohtml", "<PDF-file> <html-dir>", argDesc); + } + goto err0; + } + fileName = new GString(argv[1]); + htmlDir = argv[2]; + + // read config file + globalParams = new GlobalParams(cfgFileName); + if (quiet) { + globalParams->setErrQuiet(quiet); + } + globalParams->setupBaseFonts(NULL); + globalParams->setTextEncoding("UTF-8"); + + // open PDF file + if (ownerPassword[0] != '\001') { + ownerPW = new GString(ownerPassword); + } else { + ownerPW = NULL; + } + if (userPassword[0] != '\001') { + userPW = new GString(userPassword); + } else { + userPW = NULL; + } + doc = new PDFDoc(fileName, ownerPW, userPW); + if (userPW) { + delete userPW; + } + if (ownerPW) { + delete ownerPW; + } + if (!doc->isOk()) { + exitCode = 1; + goto err1; + } + + // check for copy permission + if (!doc->okToCopy()) { + error(errNotAllowed, -1, + "Copying of text from this document is not allowed."); + exitCode = 3; + goto err1; + } + + // get page range + if (firstPage < 1) { + firstPage = 1; + } + if (lastPage < 1 || lastPage > doc->getNumPages()) { + lastPage = doc->getNumPages(); + } + + // create HTML directory + if (!createDir(htmlDir, 0755)) { + error(errIO, -1, "Couldn't create HTML output directory '{0:s}'", + htmlDir); + exitCode = 2; + goto err1; + } + + // set up the HTMLGen object + htmlGen = new HTMLGen(resolution); + if (!htmlGen->isOk()) { + exitCode = 99; + goto err1; + } + htmlGen->setDrawInvisibleText(!skipInvisible); + htmlGen->startDoc(doc); + + // convert the pages + for (pg = firstPage; pg <= lastPage; ++pg) { + htmlFileName = GString::format("{0:s}/page{1:d}.html", htmlDir, pg); + pngFileName = GString::format("{0:s}/page{1:d}.png", htmlDir, pg); + if (!(htmlFile = fopen(htmlFileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName); + delete htmlFileName; + delete pngFileName; + goto err2; + } + if (!(pngFile = fopen(pngFileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open PNG file '{0:t}'", pngFileName); + fclose(htmlFile); + delete htmlFileName; + delete pngFileName; + goto err2; + } + pngURL = GString::format("page{0:d}.png", pg); + err = htmlGen->convertPage(pg, pngURL->getCString(), + &writeToFile, htmlFile, + &writeToFile, pngFile); + delete pngURL; + fclose(htmlFile); + fclose(pngFile); + delete htmlFileName; + delete pngFileName; + if (err != errNone) { + error(errIO, -1, "Error converting page {0:d}", pg); + exitCode = 2; + goto err2; + } + } + + // create the master index + if (!createIndex(htmlDir)) { + exitCode = 2; + goto err2; + } + + exitCode = 0; + + // clean up + err2: + delete htmlGen; + err1: + delete doc; + delete globalParams; + err0: + + // check for memory leaks + Object::memCheck(stderr); + gMemReport(stderr); + + return exitCode; +} + +static GBool createIndex(char *htmlDir) { + GString *htmlFileName; + FILE *html; + int pg; + + htmlFileName = GString::format("{0:s}/index.html", htmlDir); + html = fopen(htmlFileName->getCString(), "w"); + delete htmlFileName; + if (!html) { + error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName); + return gFalse; + } + + fprintf(html, "<html>\n"); + fprintf(html, "<body>\n"); + for (pg = firstPage; pg <= lastPage; ++pg) { + fprintf(html, "<a href=\"page%d.html\">page %d</a><br>\n", pg, pg); + } + fprintf(html, "</body>\n"); + fprintf(html, "</html>\n"); + + fclose(html); + + return gTrue; +} diff --git a/xpdf/pdftopng.cc b/xpdf/pdftopng.cc new file mode 100644 index 0000000..bb91eba --- /dev/null +++ b/xpdf/pdftopng.cc @@ -0,0 +1,289 @@ +//======================================================================== +// +// pdftopng.cc +// +// Copyright 2009 Glyph & Cog, LLC +// +//======================================================================== + +#include <aconf.h> +#include <stdlib.h> +#include <stdio.h> +#include <png.h> +#include "parseargs.h" +#include "gmem.h" +#include "GString.h" +#include "GlobalParams.h" +#include "Object.h" +#include "PDFDoc.h" +#include "SplashBitmap.h" +#include "Splash.h" +#include "SplashOutputDev.h" +#include "config.h" + +static int firstPage = 1; +static int lastPage = 0; +static int resolution = 150; +static GBool mono = gFalse; +static GBool gray = gFalse; +static char enableFreeTypeStr[16] = ""; +static char antialiasStr[16] = ""; +static char vectorAntialiasStr[16] = ""; +static char ownerPassword[33] = ""; +static char userPassword[33] = ""; +static GBool quiet = gFalse; +static char cfgFileName[256] = ""; +static GBool printVersion = gFalse; +static GBool printHelp = gFalse; + +static ArgDesc argDesc[] = { + {"-f", argInt, &firstPage, 0, + "first page to print"}, + {"-l", argInt, &lastPage, 0, + "last page to print"}, + {"-r", argInt, &resolution, 0, + "resolution, in DPI (default is 150)"}, + {"-mono", argFlag, &mono, 0, + "generate a monochrome PBM file"}, + {"-gray", argFlag, &gray, 0, + "generate a grayscale PGM file"}, +#if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H + {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), + "enable FreeType font rasterizer: yes, no"}, +#endif + {"-aa", argString, antialiasStr, sizeof(antialiasStr), + "enable font anti-aliasing: yes, no"}, + {"-aaVector", argString, vectorAntialiasStr, sizeof(vectorAntialiasStr), + "enable vector anti-aliasing: yes, no"}, + {"-opw", argString, ownerPassword, sizeof(ownerPassword), + "owner password (for encrypted files)"}, + {"-upw", argString, userPassword, sizeof(userPassword), + "user password (for encrypted files)"}, + {"-q", argFlag, &quiet, 0, + "don't print any messages or errors"}, + {"-cfg", argString, cfgFileName, sizeof(cfgFileName), + "configuration file to use in place of .xpdfrc"}, + {"-v", argFlag, &printVersion, 0, + "print copyright and version info"}, + {"-h", argFlag, &printHelp, 0, + "print usage information"}, + {"-help", argFlag, &printHelp, 0, + "print usage information"}, + {"--help", argFlag, &printHelp, 0, + "print usage information"}, + {"-?", argFlag, &printHelp, 0, + "print usage information"}, + {NULL} +}; + +static void setupPNG(png_structp *png, png_infop *pngInfo, FILE *f, + int bitDepth, int colorType, + SplashBitmap *bitmap); +static void writePNGData(png_structp png, SplashBitmap *bitmap); +static void finishPNG(png_structp *png, png_infop *pngInfo); + +int main(int argc, char *argv[]) { + PDFDoc *doc; + GString *fileName; + char *pngRoot; + GString *pngFile; + GString *ownerPW, *userPW; + SplashColor paperColor; + SplashOutputDev *splashOut; + GBool ok; + int exitCode; + int pg; + png_structp png; + png_infop pngInfo; + FILE *f; + + exitCode = 99; + + // parse args + ok = parseArgs(argDesc, &argc, argv); + if (mono && gray) { + ok = gFalse; + } + if (!ok || argc != 3 || printVersion || printHelp) { + fprintf(stderr, "pdftopng version %s\n", xpdfVersion); + fprintf(stderr, "%s\n", xpdfCopyright); + if (!printVersion) { + printUsage("pdftopng", "<PDF-file> <PNG-root>", argDesc); + } + goto err0; + } + fileName = new GString(argv[1]); + pngRoot = argv[2]; + + // read config file + globalParams = new GlobalParams(cfgFileName); + globalParams->setupBaseFonts(NULL); + if (enableFreeTypeStr[0]) { + if (!globalParams->setEnableFreeType(enableFreeTypeStr)) { + fprintf(stderr, "Bad '-freetype' value on command line\n"); + } + } + if (antialiasStr[0]) { + if (!globalParams->setAntialias(antialiasStr)) { + fprintf(stderr, "Bad '-aa' value on command line\n"); + } + } + if (vectorAntialiasStr[0]) { + if (!globalParams->setVectorAntialias(vectorAntialiasStr)) { + fprintf(stderr, "Bad '-aaVector' value on command line\n"); + } + } + if (quiet) { + globalParams->setErrQuiet(quiet); + } + + // open PDF file + if (ownerPassword[0]) { + ownerPW = new GString(ownerPassword); + } else { + ownerPW = NULL; + } + if (userPassword[0]) { + userPW = new GString(userPassword); + } else { + userPW = NULL; + } + doc = new PDFDoc(fileName, ownerPW, userPW); + if (userPW) { + delete userPW; + } + if (ownerPW) { + delete ownerPW; + } + if (!doc->isOk()) { + exitCode = 1; + goto err1; + } + + // get page range + if (firstPage < 1) + firstPage = 1; + if (lastPage < 1 || lastPage > doc->getNumPages()) + lastPage = doc->getNumPages(); + + + // write PNG files + if (mono) { + paperColor[0] = 0xff; + splashOut = new SplashOutputDev(splashModeMono1, 1, gFalse, paperColor); + } else if (gray) { + paperColor[0] = 0xff; + splashOut = new SplashOutputDev(splashModeMono8, 1, gFalse, paperColor); + } else { + paperColor[0] = paperColor[1] = paperColor[2] = 0xff; + splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse, paperColor); + } + splashOut->startDoc(doc->getXRef()); + for (pg = firstPage; pg <= lastPage; ++pg) { + doc->displayPage(splashOut, pg, resolution, resolution, 0, + gFalse, gTrue, gFalse); + if (mono) { + if (!strcmp(pngRoot, "-")) { + f = stdout; + } else { + pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); + if (!(f = fopen(pngFile->getCString(), "wb"))) { + exit(2); + } + delete pngFile; + } + setupPNG(&png, &pngInfo, f, + 1, PNG_COLOR_TYPE_GRAY, splashOut->getBitmap()); + writePNGData(png, splashOut->getBitmap()); + finishPNG(&png, &pngInfo); + fclose(f); + } else if (gray) { + if (!strcmp(pngRoot, "-")) { + f = stdout; + } else { + pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); + if (!(f = fopen(pngFile->getCString(), "wb"))) { + exit(2); + } + delete pngFile; + } + setupPNG(&png, &pngInfo, f, + 8, PNG_COLOR_TYPE_GRAY, splashOut->getBitmap()); + writePNGData(png, splashOut->getBitmap()); + finishPNG(&png, &pngInfo); + fclose(f); + } else { // RGB + if (!strcmp(pngRoot, "-")) { + f = stdout; + } else { + pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); + if (!(f = fopen(pngFile->getCString(), "wb"))) { + exit(2); + } + delete pngFile; + } + setupPNG(&png, &pngInfo, f, + 8, PNG_COLOR_TYPE_RGB, splashOut->getBitmap()); + writePNGData(png, splashOut->getBitmap()); + finishPNG(&png, &pngInfo); + fclose(f); + } + } + delete splashOut; + + exitCode = 0; + + // clean up + err1: + delete doc; + delete globalParams; + err0: + + // check for memory leaks + Object::memCheck(stderr); + gMemReport(stderr); + + return exitCode; +} + +static void setupPNG(png_structp *png, png_infop *pngInfo, FILE *f, + int bitDepth, int colorType, + SplashBitmap *bitmap) { + if (!(*png = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL)) || + !(*pngInfo = png_create_info_struct(*png))) { + exit(2); + } + if (setjmp(png_jmpbuf(*png))) { + exit(2); + } + png_init_io(*png, f); + png_set_IHDR(*png, *pngInfo, bitmap->getWidth(), bitmap->getHeight(), + bitDepth, colorType, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(*png, *pngInfo); +} + +static void writePNGData(png_structp png, SplashBitmap *bitmap) { + Guchar *p; + int y; + + if (setjmp(png_jmpbuf(png))) { + exit(2); + } + p = bitmap->getDataPtr(); + for (y = 0; y < bitmap->getHeight(); ++y) { + png_write_row(png, (png_bytep)p); + p += bitmap->getRowSize(); + } +} + + + +static void finishPNG(png_structp *png, png_infop *pngInfo) { + if (setjmp(png_jmpbuf(*png))) { + exit(2); + } + png_write_end(*png, *pngInfo); + png_destroy_write_struct(png, pngInfo); +} diff --git a/xpdf/pdftoppm.cc b/xpdf/pdftoppm.cc index cfc7236..17c0bbf 100644 --- a/xpdf/pdftoppm.cc +++ b/xpdf/pdftoppm.cc @@ -8,6 +8,14 @@ #include <aconf.h> #include <stdio.h> +#ifdef _WIN32 +# include <io.h> +# include <fcntl.h> +#endif +#ifdef DEBUG_FP_LINUX +# include <fenv.h> +# include <fpu_control.h> +#endif #include "parseargs.h" #include "gmem.h" #include "GString.h" @@ -24,7 +32,6 @@ static int lastPage = 0; static int resolution = 150; static GBool mono = gFalse; static GBool gray = gFalse; -static char enableT1libStr[16] = ""; static char enableFreeTypeStr[16] = ""; static char antialiasStr[16] = ""; static char vectorAntialiasStr[16] = ""; @@ -46,10 +53,6 @@ static ArgDesc argDesc[] = { "generate a monochrome PBM file"}, {"-gray", argFlag, &gray, 0, "generate a grayscale PGM file"}, -#if HAVE_T1LIB_H - {"-t1lib", argString, enableT1libStr, sizeof(enableT1libStr), - "enable t1lib font rasterizer: yes, no"}, -#endif #if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no"}, @@ -91,6 +94,21 @@ int main(int argc, char *argv[]) { int exitCode; int pg; +#ifdef DEBUG_FP_LINUX + // enable exceptions on floating point div-by-zero + feenableexcept(FE_DIVBYZERO); + // force 64-bit rounding: this avoids changes in output when minor + // code changes result in spills of x87 registers; it also avoids + // differences in output with valgrind's 64-bit floating point + // emulation (yes, this is a kludge; but it's pretty much + // unavoidable given the x87 instruction set; see gcc bug 323 for + // more info) + fpu_control_t cw; + _FPU_GETCW(cw); + cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(cw); +#endif + exitCode = 99; // parse args @@ -112,11 +130,6 @@ int main(int argc, char *argv[]) { // read config file globalParams = new GlobalParams(cfgFileName); globalParams->setupBaseFonts(NULL); - if (enableT1libStr[0]) { - if (!globalParams->setEnableT1lib(enableT1libStr)) { - fprintf(stderr, "Bad '-t1lib' value on command line\n"); - } - } if (enableFreeTypeStr[0]) { if (!globalParams->setEnableFreeType(enableFreeTypeStr)) { fprintf(stderr, "Bad '-freetype' value on command line\n"); @@ -182,6 +195,9 @@ int main(int argc, char *argv[]) { doc->displayPage(splashOut, pg, resolution, resolution, 0, gFalse, gTrue, gFalse); if (!strcmp(ppmRoot, "-")) { +#ifdef _WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif splashOut->getBitmap()->writePNMFile(stdout); } else { ppmFile = GString::format("{0:s}-{1:06d}.{2:s}", diff --git a/xpdf/pdftops.cc b/xpdf/pdftops.cc index eae441f..a6f478d 100644 --- a/xpdf/pdftops.cc +++ b/xpdf/pdftops.cc @@ -11,6 +11,10 @@ #include <stdlib.h> #include <stddef.h> #include <string.h> +#ifdef DEBUG_FP_LINUX +# include <fenv.h> +# include <fpu_control.h> +#endif #include "parseargs.h" #include "GString.h" #include "gmem.h" @@ -147,6 +151,21 @@ int main(int argc, char *argv[]) { char *p; int exitCode; +#ifdef DEBUG_FP_LINUX + // enable exceptions on floating point div-by-zero + feenableexcept(FE_DIVBYZERO); + // force 64-bit rounding: this avoids changes in output when minor + // code changes result in spills of x87 registers; it also avoids + // differences in output with valgrind's 64-bit floating point + // emulation (yes, this is a kludge; but it's pretty much + // unavoidable given the x87 instruction set; see gcc bug 323 for + // more info) + fpu_control_t cw; + _FPU_GETCW(cw); + cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(cw); +#endif + exitCode = 99; // parse args @@ -216,6 +235,9 @@ int main(int argc, char *argv[]) { if (noCrop) { globalParams->setPSCrop(gFalse); } + if (pageCrop) { + globalParams->setPSUseCropBoxAsPage(gTrue); + } if (expand) { globalParams->setPSExpandSmaller(gTrue); } @@ -318,15 +340,18 @@ int main(int argc, char *argv[]) { firstPage, lastPage, mode); if (psOut->isOk()) { doc->displayPages(psOut, firstPage, lastPage, 72, 72, - 0, !pageCrop, globalParams->getPSCrop(), gTrue); + 0, !globalParams->getPSUseCropBoxAsPage(), + globalParams->getPSCrop(), gTrue); } else { delete psOut; exitCode = 2; goto err2; } - delete psOut; - exitCode = 0; + if (!psOut->checkIO()) { + exitCode = 2; + } + delete psOut; // clean up err2: diff --git a/xpdf/pdftotext.cc b/xpdf/pdftotext.cc index 5296ac4..758413e 100644 --- a/xpdf/pdftotext.cc +++ b/xpdf/pdftotext.cc @@ -2,7 +2,7 @@ // // pdftotext.cc // -// Copyright 1997-2003 Glyph & Cog, LLC +// Copyright 1997-2013 Glyph & Cog, LLC // //======================================================================== @@ -11,6 +11,10 @@ #include <stdlib.h> #include <stddef.h> #include <string.h> +#ifdef DEBUG_FP_LINUX +# include <fenv.h> +# include <fpu_control.h> +#endif #include "parseargs.h" #include "GString.h" #include "gmem.h" @@ -26,21 +30,19 @@ #include "TextOutputDev.h" #include "CharTypes.h" #include "UnicodeMap.h" +#include "TextString.h" #include "Error.h" #include "config.h" -static void printInfoString(FILE *f, Dict *infoDict, const char *key, - const char *text1, const char *text2, - UnicodeMap *uMap); -static void printInfoDate(FILE *f, Dict *infoDict, const char *key, - const char *fmt); - static int firstPage = 1; static int lastPage = 0; static GBool physLayout = gFalse; -static double fixedPitch = 0; +static GBool tableLayout = gFalse; +static GBool linePrinter = gFalse; static GBool rawOrder = gFalse; -static GBool htmlMeta = gFalse; +static double fixedPitch = 0; +static double fixedLineSpacing = 0; +static GBool clipText = gFalse; static char textEncName[128] = ""; static char textEOL[16] = ""; static GBool noPageBreaks = gFalse; @@ -58,12 +60,18 @@ static ArgDesc argDesc[] = { "last page to convert"}, {"-layout", argFlag, &physLayout, 0, "maintain original physical layout"}, - {"-fixed", argFP, &fixedPitch, 0, - "assume fixed-pitch (or tabular) text"}, + {"-table", argFlag, &tableLayout, 0, + "similar to -layout, but optimized for tables"}, + {"-lineprinter", argFlag, &linePrinter, 0, + "use strict fixed-pitch/height layout"}, {"-raw", argFlag, &rawOrder, 0, "keep strings in content stream order"}, - {"-htmlmeta", argFlag, &htmlMeta, 0, - "generate a simple HTML file, including the meta information"}, + {"-fixed", argFP, &fixedPitch, 0, + "assume fixed-pitch (or tabular) text"}, + {"-linespacing", argFP, &fixedLineSpacing, 0, + "fixed line spacing for LinePrinter mode"}, + {"-clip", argFlag, &clipText, 0, + "separate clipped text"}, {"-enc", argString, textEncName, sizeof(textEncName), "output text encoding name"}, {"-eol", argString, textEOL, sizeof(textEOL), @@ -96,14 +104,29 @@ int main(int argc, char *argv[]) { GString *fileName; GString *textFileName; GString *ownerPW, *userPW; + TextOutputControl textOutControl; TextOutputDev *textOut; - FILE *f; UnicodeMap *uMap; Object info; GBool ok; char *p; int exitCode; +#ifdef DEBUG_FP_LINUX + // enable exceptions on floating point div-by-zero + feenableexcept(FE_DIVBYZERO); + // force 64-bit rounding: this avoids changes in output when minor + // code changes result in spills of x87 registers; it also avoids + // differences in output with valgrind's 64-bit floating point + // emulation (yes, this is a kludge; but it's pretty much + // unavoidable given the x87 instruction set; see gcc bug 323 for + // more info) + fpu_control_t cw; + _FPU_GETCW(cw); + cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(cw); +#endif + exitCode = 99; // parse args @@ -117,9 +140,6 @@ int main(int argc, char *argv[]) { goto err0; } fileName = new GString(argv[1]); - if (fixedPitch) { - physLayout = gTrue; - } // read config file globalParams = new GlobalParams(cfgFileName); @@ -187,7 +207,7 @@ int main(int argc, char *argv[]) { } else { textFileName = fileName->copy(); } - textFileName->append(htmlMeta ? ".html" : ".txt"); + textFileName->append(".txt"); } // get page range @@ -198,50 +218,25 @@ int main(int argc, char *argv[]) { lastPage = doc->getNumPages(); } - // write HTML header - if (htmlMeta) { - if (!textFileName->cmp("-")) { - f = stdout; - } else { - if (!(f = fopen(textFileName->getCString(), "wb"))) { - error(errIO, -1, "Couldn't open text file '{0:t}'", textFileName); - exitCode = 2; - goto err3; - } - } - fputs("<html>\n", f); - fputs("<head>\n", f); - doc->getDocInfo(&info); - if (info.isDict()) { - printInfoString(f, info.getDict(), "Title", "<title>", "</title>\n", - uMap); - printInfoString(f, info.getDict(), "Subject", - "<meta name=\"Subject\" content=\"", "\">\n", uMap); - printInfoString(f, info.getDict(), "Keywords", - "<meta name=\"Keywords\" content=\"", "\">\n", uMap); - printInfoString(f, info.getDict(), "Author", - "<meta name=\"Author\" content=\"", "\">\n", uMap); - printInfoString(f, info.getDict(), "Creator", - "<meta name=\"Creator\" content=\"", "\">\n", uMap); - printInfoString(f, info.getDict(), "Producer", - "<meta name=\"Producer\" content=\"", "\">\n", uMap); - printInfoDate(f, info.getDict(), "CreationDate", - "<meta name=\"CreationDate\" content=\"%s\">\n"); - printInfoDate(f, info.getDict(), "LastModifiedDate", - "<meta name=\"ModDate\" content=\"%s\">\n"); - } - info.free(); - fputs("</head>\n", f); - fputs("<body>\n", f); - fputs("<pre>\n", f); - if (f != stdout) { - fclose(f); - } - } - // write text file - textOut = new TextOutputDev(textFileName->getCString(), - physLayout, fixedPitch, rawOrder, htmlMeta); + if (tableLayout) { + textOutControl.mode = textOutTableLayout; + textOutControl.fixedPitch = fixedPitch; + } else if (physLayout) { + textOutControl.mode = textOutPhysLayout; + textOutControl.fixedPitch = fixedPitch; + } else if (linePrinter) { + textOutControl.mode = textOutLinePrinter; + textOutControl.fixedPitch = fixedPitch; + textOutControl.fixedLineSpacing = fixedLineSpacing; + } else if (rawOrder) { + textOutControl.mode = textOutRawOrder; + } else { + textOutControl.mode = textOutReadingOrder; + } + textOutControl.clipText = clipText; + textOut = new TextOutputDev(textFileName->getCString(), &textOutControl, + gFalse); if (textOut->isOk()) { doc->displayPages(textOut, firstPage, lastPage, 72, 72, 0, gFalse, gTrue, gFalse); @@ -252,25 +247,6 @@ int main(int argc, char *argv[]) { } delete textOut; - // write end of HTML file - if (htmlMeta) { - if (!textFileName->cmp("-")) { - f = stdout; - } else { - if (!(f = fopen(textFileName->getCString(), "ab"))) { - error(errIO, -1, "Couldn't open text file '{0:t}'", textFileName); - exitCode = 2; - goto err3; - } - } - fputs("</pre>\n", f); - fputs("</body>\n", f); - fputs("</html>\n", f); - if (f != stdout) { - fclose(f); - } - } - exitCode = 0; // clean up @@ -289,56 +265,3 @@ int main(int argc, char *argv[]) { return exitCode; } - -static void printInfoString(FILE *f, Dict *infoDict, const char *key, - const char *text1, const char *text2, - UnicodeMap *uMap) { - Object obj; - GString *s1; - GBool isUnicode; - Unicode u; - char buf[8]; - int i, n; - - if (infoDict->lookup(key, &obj)->isString()) { - fputs(text1, f); - s1 = obj.getString(); - if ((s1->getChar(0) & 0xff) == 0xfe && - (s1->getChar(1) & 0xff) == 0xff) { - isUnicode = gTrue; - i = 2; - } else { - isUnicode = gFalse; - i = 0; - } - while (i < obj.getString()->getLength()) { - if (isUnicode) { - u = ((s1->getChar(i) & 0xff) << 8) | - (s1->getChar(i+1) & 0xff); - i += 2; - } else { - u = s1->getChar(i) & 0xff; - ++i; - } - n = uMap->mapUnicode(u, buf, sizeof(buf)); - fwrite(buf, 1, n, f); - } - fputs(text2, f); - } - obj.free(); -} - -static void printInfoDate(FILE *f, Dict *infoDict, const char *key, - const char *fmt) { - Object obj; - char *s; - - if (infoDict->lookup(key, &obj)->isString()) { - s = obj.getString()->getCString(); - if (s[0] == 'D' && s[1] == ':') { - s += 2; - } - fprintf(f, fmt, s); - } - obj.free(); -} diff --git a/xpdf/vms_make.com b/xpdf/vms_make.com deleted file mode 100644 index f4fb74a..0000000 --- a/xpdf/vms_make.com +++ /dev/null @@ -1,129 +0,0 @@ -$!======================================================================== -$! -$! Xpdf compile script for VMS. -$! -$! Written by Patrick Moreau, Martin P.J. Zinser. -$! -$! Copyright 1996-2003 Glyph & Cog, LLC -$! -$!======================================================================== -$! -$ i = 0 -$ j = 0 -$ APPS = "XPDF,PDFTOPS,PDFTOTEXT,PDFINFO,PDFTOPBM,PDFIMAGES,PDFFONTS" -$ if f$search("COMMON.OLB").eqs."" then lib/create common.olb -$! -$ COMMON_OBJS = "Annot.obj,Array.obj,BuiltinFont.obj," + - - "BuiltinFontTables.obj,Catalog.obj,CharCodeToUnicode.obj," + - - "CMap.obj,Decrypt.obj,Dict.obj,Error.obj," + - - "FontEncodingTables.obj,FontFile.obj," + - - "Function.obj,Gfx.obj,GfxFont.obj,GfxState.obj,"+ - - "GlobalParams.obj,JArithmeticDecoder.obj,JBIG2Stream.obj,"+ - - "Lexer.obj,Link.obj,NameToCharCode.obj,Object.obj,"+ - - "Outline.obj,OutputDev.obj,Page.obj,Parser.obj,PDFdoc.obj," + - - "PDFDocEncoding.obj,PSTokenizer.obj,Stream.obj," + - - "UnicodeMap.obj,UnicodeTypeTable.obj,XRef.obj" -$ COMMON_LIBS = "[]common.olb/lib,[-.goo]libgoo.olb/lib" -$! -$ XPDF_OBJS = "xpdf.obj,FTFont.obj,PSOutputDev.obj," + - - "SFont.obj,T1Font.obj,TextOutputDev.obj,TTFont.obj," + - - "XOutputDev.obj,XPDFApp.obj,XPDFCore.obj,XPDFTree.obj," + - - "XPDFViewer.obj,XPixmapOutputDev.obj" -$ XPDF_LIBS = "" -$! -$ PDFTOPS_OBJS = "pdftops.obj,PSOutputDev.obj" -$ PDFTOPS_LIBS = "" -$! -$ PDFTOTEXT_OBJS = "pdftotext.obj,TextOutputDev.obj" -$ PDFTOTEXT_LIBS = "" -$! -$ PDFINFO_OBJS = "pdfinfo.obj" -$ PDFINFO_LIBS = "" -$! -$ PDFTOPBM_OBJS = "pdftopbm.obj,FTFont.obj,PBMOutputDev.obj,SFont.obj," + - - "T1Font.obj,TextOutputDev.obj,TTFont.obj,XOutputDev.obj" -$ PDFTOPBM_LIBS = "" -$! -$ PDFIMAGES_OBJS = "pdfimages.obj,ImageOutputDev.obj" -$ PDFIMAGES_LIBS = "" -$! -$ PDFFONTS_OBJS = "pdffonts.obj" -$ PDFFONTS_LIBS = "" -$! -$COMPILE_CXX_LOOP: -$ file = f$element(i, ",",COMMON_OBJS) -$ if file .eqs. "," then goto BUILD_APPS -$ i = i + 1 -$ name = f$parse(file,,,"NAME") -$ call make 'file "CXXCOMP ''name'.cc" - - 'name'.cc -$ call make common.olb "lib/replace common.olb ''name'.obj" - - 'name'.obj -$ goto COMPILE_CXX_LOOP -$! -$BUILD_APPS: -$ curr_app = f$element(j,",",APPS) -$ if curr_app .eqs. "," then exit -$ j = j + 1 -$ i = 0 -$COMPILE_APP: -$ file = f$element(i,",",'curr_app'_OBJS) -$ if file .eqs. "," then goto LINK_APP -$ i = i + 1 -$ name = f$parse(file,,,"NAME") -$ call make 'file "CXXCOMP ''name'.cc" - - 'name'.cc -$ goto COMPILE_APP -$LINK_APP: -$ if 'curr_app'_LIBS .nes. "" -$ then -$ LIBS = 'curr_app'_LIBS + "," + COMMON_LIBS -$ else -$ LIBS = COMMON_LIBS -$ endif -$ OBJS = 'curr_app'_OBJS -$ write sys$output "Linking ''curr_app'..." -$ xpdf_link/exe='curr_app'.exe 'OBJS','libs',[-]xpdf.opt/opt -$! -$ goto BUILD_APPS -$ exit -$! -$MAKE: SUBROUTINE !SUBROUTINE TO CHECK DEPENDENCIES -$ V = 'F$Verify(0) -$! P1 = What we are trying to make -$! P2 = Command to make it -$! P3 - P8 What it depends on -$ -$ If F$Search(P1) .Eqs. "" Then Goto Makeit -$ Time = F$CvTime(F$File(P1,"RDT")) -$arg=3 -$Loop: -$ Argument = P'arg -$ If Argument .Eqs. "" Then Goto Exit -$ El=0 -$Loop2: -$ File = F$Element(El," ",Argument) -$ If File .Eqs. " " Then Goto Endl -$ AFile = "" -$Loop3: -$ OFile = AFile -$ AFile = F$Search(File) -$ If AFile .Eqs. "" .Or. AFile .Eqs. OFile Then Goto NextEl -$ If F$CvTime(F$File(AFile,"RDT")) .Ges. Time Then Goto Makeit -$ Goto Loop3 -$NextEL: -$ El = El + 1 -$ Goto Loop2 -$EndL: -$ arg=arg+1 -$ If arg .Le. 8 Then Goto Loop -$ Goto Exit -$ -$Makeit: -$ VV=F$VERIFY(0) -$ write sys$output P2 -$ 'P2 -$ VV='F$Verify(VV) -$Exit: -$ If V Then Set Verify -$ENDSUBROUTINE diff --git a/xpdf/xpdf.cc b/xpdf/xpdf.cc index e5d91ca..1041265 100644 --- a/xpdf/xpdf.cc +++ b/xpdf/xpdf.cc @@ -22,7 +22,6 @@ //------------------------------------------------------------------------ static GBool contView = gFalse; -static char enableT1libStr[16] = ""; static char enableFreeTypeStr[16] = ""; static char antialiasStr[16] = ""; static char vectorAntialiasStr[16] = ""; @@ -66,10 +65,6 @@ static ArgDesc argDesc[] = { "initial zoom level (percent, 'page', 'width')"}, {"-cont", argFlag, &contView, 0, "start in continuous view mode" }, -#if HAVE_T1LIB_H - {"-t1lib", argString, enableT1libStr, sizeof(enableT1libStr), - "enable t1lib font rasterizer: yes, no"}, -#endif #if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no"}, @@ -185,11 +180,6 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Bad '-eol' value on command line\n"); } } - if (enableT1libStr[0]) { - if (!globalParams->setEnableT1lib(enableT1libStr)) { - fprintf(stderr, "Bad '-t1lib' value on command line\n"); - } - } if (enableFreeTypeStr[0]) { if (!globalParams->setEnableFreeType(enableFreeTypeStr)) { fprintf(stderr, "Bad '-freetype' value on command line\n"); |