//======================================================================== // // XOutputDev.cc // // Copyright 1996 Derek B. Noonburg // //======================================================================== #ifdef __GNUC__ #pragma implementation #endif #include #include #include #include #include #include #include "gmem.h" #include "GString.h" #include "Object.h" #include "Stream.h" #include "GfxState.h" #include "GfxFont.h" #include "Error.h" #include "Params.h" #include "TextOutputDev.h" #include "XOutputDev.h" #include "XOutputFontInfo.h" #ifdef XlibSpecificationRelease #if XlibSpecificationRelease < 5 typedef char *XPointer; #endif #else typedef char *XPointer; #endif //------------------------------------------------------------------------ // Constants and macros //------------------------------------------------------------------------ #define xoutRound(x) ((int)(x + 0.5)) #define maxCurveSplits 6 // max number of splits when recursively // drawing Bezier curves //------------------------------------------------------------------------ // Parameters //------------------------------------------------------------------------ GBool installCmap; int rgbCubeSize; //------------------------------------------------------------------------ // Font map //------------------------------------------------------------------------ struct FontMapEntry { char *pdfFont; char *xFont; GfxFontEncoding *encoding; }; static FontMapEntry fontMap[] = { {"Courier", "-*-courier-medium-r-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Courier-Bold", "-*-courier-bold-r-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Courier-BoldOblique", "-*-courier-bold-o-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Courier-Oblique", "-*-courier-medium-o-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Helvetica", "-*-helvetica-medium-r-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Helvetica-Bold", "-*-helvetica-bold-r-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Helvetica-BoldOblique", "-*-helvetica-bold-o-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Helvetica-Oblique", "-*-helvetica-medium-o-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Symbol", "-*-symbol-medium-r-normal-*-%s-*-*-*-*-*-adobe-fontspecific", &symbolEncoding}, {"Times-Bold", "-*-times-bold-r-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Times-BoldItalic", "-*-times-bold-i-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Times-Italic", "-*-times-medium-i-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"Times-Roman", "-*-times-medium-r-normal-*-%s-*-*-*-*-*-iso8859-1", &isoLatin1Encoding}, {"ZapfDingbats", "-*-zapfdingbats-medium-r-normal-*-%s-*-*-*-*-*-*-*", &zapfDingbatsEncoding}, {NULL} }; static FontMapEntry *userFontMap; //------------------------------------------------------------------------ // Font substitutions //------------------------------------------------------------------------ struct FontSubst { char *xFont; double mWidth; }; // index: {symbolic:12, fixed:8, serif:4, sans-serif:0} + bold*2 + italic static FontSubst fontSubst[16] = { {"-*-helvetica-medium-r-normal-*-%s-*-*-*-*-*-iso8859-1", 0.833}, {"-*-helvetica-medium-o-normal-*-%s-*-*-*-*-*-iso8859-1", 0.833}, {"-*-helvetica-bold-r-normal-*-%s-*-*-*-*-*-iso8859-1", 0.889}, {"-*-helvetica-bold-o-normal-*-%s-*-*-*-*-*-iso8859-1", 0.889}, {"-*-times-medium-r-normal-*-%s-*-*-*-*-*-iso8859-1", 0.788}, {"-*-times-medium-i-normal-*-%s-*-*-*-*-*-iso8859-1", 0.722}, {"-*-times-bold-r-normal-*-%s-*-*-*-*-*-iso8859-1", 0.833}, {"-*-times-bold-i-normal-*-%s-*-*-*-*-*-iso8859-1", 0.778}, {"-*-courier-medium-r-normal-*-%s-*-*-*-*-*-iso8859-1", 0.600}, {"-*-courier-medium-o-normal-*-%s-*-*-*-*-*-iso8859-1", 0.600}, {"-*-courier-bold-r-normal-*-%s-*-*-*-*-*-iso8859-1", 0.600}, {"-*-courier-bold-o-normal-*-%s-*-*-*-*-*-iso8859-1", 0.600}, {"-*-symbol-medium-r-normal-*-%s-*-*-*-*-*-adobe-fontspecific", 0.576}, {"-*-symbol-medium-r-normal-*-%s-*-*-*-*-*-adobe-fontspecific", 0.576}, {"-*-symbol-medium-r-normal-*-%s-*-*-*-*-*-adobe-fontspecific", 0.576}, {"-*-symbol-medium-r-normal-*-%s-*-*-*-*-*-adobe-fontspecific", 0.576} }; //------------------------------------------------------------------------ // 16-bit fonts //------------------------------------------------------------------------ #if JAPANESE_SUPPORT static char *japan12Font = "-*-fixed-medium-r-normal-*-%s-*-*-*-*-*-jisx0208.1983-0"; // CID 0 .. 96 static Gushort japan12Map[96] = { 0x2120, 0x2120, 0x212a, 0x2149, 0x2174, 0x2170, 0x2173, 0x2175, // 00 .. 07 0x2147, 0x214a, 0x214b, 0x2176, 0x215c, 0x2124, 0x213e, 0x2123, // 08 .. 0f 0x213f, 0x2330, 0x2331, 0x2332, 0x2333, 0x2334, 0x2335, 0x2336, // 10 .. 17 0x2337, 0x2338, 0x2339, 0x2127, 0x2128, 0x2163, 0x2161, 0x2164, // 18 .. 1f 0x2129, 0x2177, 0x2341, 0x2342, 0x2343, 0x2344, 0x2345, 0x2346, // 20 .. 27 0x2347, 0x2348, 0x2349, 0x234a, 0x234b, 0x234c, 0x234d, 0x234e, // 28 .. 2f 0x234f, 0x2350, 0x2351, 0x2352, 0x2353, 0x2354, 0x2355, 0x2356, // 30 .. 37 0x2357, 0x2358, 0x2359, 0x235a, 0x214e, 0x216f, 0x214f, 0x2130, // 38 .. 3f 0x2132, 0x2146, 0x2361, 0x2362, 0x2363, 0x2364, 0x2365, 0x2366, // 40 .. 47 0x2367, 0x2368, 0x2369, 0x236a, 0x236b, 0x236c, 0x236d, 0x236e, // 48 .. 4f 0x236f, 0x2370, 0x2371, 0x2372, 0x2373, 0x2374, 0x2375, 0x2376, // 50 .. 57 0x2377, 0x2378, 0x2379, 0x237a, 0x2150, 0x2143, 0x2151, 0x2141 // 58 .. 5f }; // CID 325 .. 421 static Gushort japan12KanaMap1[97] = { 0x2131, 0x2121, 0x2123, 0x2156, 0x2157, 0x2122, 0x2126, 0x2572, 0x2521, 0x2523, 0x2525, 0x2527, 0x2529, 0x2563, 0x2565, 0x2567, 0x2543, 0x213c, 0x2522, 0x2524, 0x2526, 0x2528, 0x252a, 0x252b, 0x252d, 0x252f, 0x2531, 0x2533, 0x2535, 0x2537, 0x2539, 0x253b, 0x253d, 0x253f, 0x2541, 0x2544, 0x2546, 0x2548, 0x254a, 0x254b, 0x254c, 0x254d, 0x254e, 0x254f, 0x2552, 0x2555, 0x2558, 0x255b, 0x255e, 0x255f, 0x2560, 0x2561, 0x2562, 0x2564, 0x2566, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x256d, 0x256f, 0x2573, 0x212b, 0x212c, 0x212e, 0x2570, 0x2571, 0x256e, 0x2575, 0x2576, 0x2574, 0x252c, 0x252e, 0x2530, 0x2532, 0x2534, 0x2536, 0x2538, 0x253a, 0x253c, 0x253e, 0x2540, 0x2542, 0x2545, 0x2547, 0x2549, 0x2550, 0x2551, 0x2553, 0x2554, 0x2556, 0x2557, 0x2559, 0x255a, 0x255c, 0x255d }; // CID 501 .. 598 static Gushort japan12KanaMap2[98] = { 0x212d, 0x212f, 0x216d, 0x214c, 0x214d, 0x2152, 0x2153, 0x2154, 0x2155, 0x2158, 0x2159, 0x215a, 0x215b, 0x213d, 0x2121, 0x2472, 0x2421, 0x2423, 0x2425, 0x2427, 0x2429, 0x2463, 0x2465, 0x2467, 0x2443, 0x2422, 0x2424, 0x2426, 0x2428, 0x242a, 0x242b, 0x242d, 0x242f, 0x2431, 0x2433, 0x2435, 0x2437, 0x2439, 0x243b, 0x243d, 0x243f, 0x2441, 0x2444, 0x2446, 0x2448, 0x244a, 0x244b, 0x244c, 0x244d, 0x244e, 0x244f, 0x2452, 0x2455, 0x2458, 0x245b, 0x245e, 0x245f, 0x2460, 0x2461, 0x2462, 0x2464, 0x2466, 0x2468, 0x2469, 0x246a, 0x246b, 0x246c, 0x246d, 0x246f, 0x2473, 0x2470, 0x2471, 0x246e, 0x242c, 0x242e, 0x2430, 0x2432, 0x2434, 0x2436, 0x2438, 0x243a, 0x243c, 0x243e, 0x2440, 0x2442, 0x2445, 0x2447, 0x2449, 0x2450, 0x2451, 0x2453, 0x2454, 0x2456, 0x2457, 0x2459, 0x245a, 0x245c, 0x245d }; static char *japan12Roman[10] = { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X" }; static char *japan12Abbrev1[6] = { "mm", "cm", "km", "mg", "kg", "cc" }; #endif //------------------------------------------------------------------------ // Constructed characters //------------------------------------------------------------------------ #define lastRegularChar 0x0ff #define firstSubstChar 0x100 #define lastSubstChar 0x104 #define firstConstrChar 0x105 #define lastConstrChar 0x106 #define firstMultiChar 0x107 #define lastMultiChar 0x10d // substituted chars static Guchar substChars[] = { 0x27, // 100: quotesingle --> quoteright 0x2d, // 101: emdash --> hyphen 0xad, // 102: hyphen --> endash 0x2f, // 103: fraction --> slash 0xb0, // 104: ring --> degree }; // constructed chars // 105: bullet // 106: trademark // built-up chars static char *multiChars[] = { "fi", // 107: fi "fl", // 108: fl "OE", // 109: OE "oe", // 10a: oe "...", // 10b: ellipsis "``", // 10c: quotedblleft "''" // 10d: quotedblright }; // ignored chars // 10c: Lslash // 10d: Scaron // 10e: Zcaron // 10f: Ydieresis // 110: breve // 111: caron // 112: circumflex // 113: dagger // 114: daggerdbl // 115: dotaccent // 116: dotlessi // 117: florin // 118: grave // 119: guilsinglleft // 11a: guilsinglright // 11b: hungarumlaut // 11c: lslash // 11d: ogonek // 11e: perthousand // 11f: quotedblbase // 120: quotesinglbase // 121: scaron // 122: tilde // 123: zcaron //------------------------------------------------------------------------ // XOutputFont //------------------------------------------------------------------------ // Note: if real font is substantially narrower than substituted // font, the size is reduced accordingly. XOutputFont::XOutputFont(GfxFont *gfxFont, double m11, double m12, double m21, double m22, Display *display1) { GString *pdfFont; FontMapEntry *p; GfxFontEncoding *encoding; char *fontNameFmt; char fontName[200], fontSize[100]; GBool rotated; double size; int startSize, sz; int index; int code, code2; double w1, w2, v; double *fm; char *charName; int n; // init id = gfxFont->getID(); mat11 = m11; mat12 = m12; mat21 = m21; mat22 = m22; display = display1; xFont = NULL; hex = gFalse; // construct X font name if (gfxFont->is16Bit()) { fontNameFmt = fontSubst[0].xFont; switch (gfxFont->getCharSet16()) { case font16AdobeJapan12: #if JAPANESE_SUPPORT fontNameFmt = japan12Font; #endif break; } } else { pdfFont = gfxFont->getName(); if (pdfFont) { for (p = userFontMap; p->pdfFont; ++p) { if (!pdfFont->cmp(p->pdfFont)) break; } if (!p->pdfFont) { for (p = fontMap; p->pdfFont; ++p) { if (!pdfFont->cmp(p->pdfFont)) break; } } } else { p = NULL; } if (p && p->pdfFont) { fontNameFmt = p->xFont; encoding = p->encoding; } else { encoding = &isoLatin1Encoding; //~ Some non-symbolic fonts are tagged as symbolic. // if (gfxFont->isSymbolic()) { // index = 12; // encoding = symbolEncoding; // } else if (gfxFont->isFixedWidth()) { index = 8; } else if (gfxFont->isSerif()) { index = 4; } else { index = 0; } if (gfxFont->isBold()) index += 2; if (gfxFont->isItalic()) index += 1; if ((code = gfxFont->getCharCode("m")) >= 0) w1 = gfxFont->getWidth(code); else w1 = 0; w2 = fontSubst[index].mWidth; if (gfxFont->getType() == fontType3) { // This is a hack which makes it possible to substitute for some // Type 3 fonts. The problem is that it's impossible to know what // the base coordinate system used in the font is without actually // rendering the font. This code tries to guess by looking at the // width of the character 'm' (which breaks if the font is a // subset that doesn't contain 'm'). if (w1 > 0 && (w1 > 1.1 * w2 || w1 < 0.9 * w2)) { w1 /= w2; mat11 *= w1; mat12 *= w1; mat21 *= w1; mat22 *= w1; } fm = gfxFont->getFontMatrix(); v = (fm[0] == 0) ? 1 : (fm[3] / fm[0]); mat12 *= v; mat22 *= v; } else if (!gfxFont->isSymbolic()) { if (w1 > 0.01 && w1 < 0.9 * w2) { w1 /= w2; if (w1 < 0.8) w1 = 0.8; mat11 *= w1; mat12 *= w1; mat21 *= w1; mat22 *= w1; } } fontNameFmt = fontSubst[index].xFont; } // Construct forward and reverse map. // This tries to deal with font subset character names of the // form 'Bxx', 'Cxx', 'Gxx', with decimal or hex numbering. for (code = 0; code < 256; ++code) revMap[code] = 0; if (encoding) { for (code = 0; code < 256; ++code) { if ((charName = gfxFont->getCharName(code))) { if ((charName[0] == 'B' || charName[0] == 'C' || charName[0] == 'G') && strlen(charName) == 3 && ((charName[1] >= 'a' && charName[1] <= 'f') || (charName[1] >= 'A' && charName[1] <= 'F') || (charName[2] >= 'a' && charName[2] <= 'f') || (charName[2] >= 'A' && charName[2] <= 'F'))) { hex = gTrue; break; } } } for (code = 0; code < 256; ++code) { if ((charName = gfxFont->getCharName(code))) { if ((code2 = encoding->getCharCode(charName)) < 0) { n = strlen(charName); if (hex && n == 3 && (charName[0] == 'B' || charName[0] == 'C' || charName[0] == 'G') && isxdigit(charName[1]) && isxdigit(charName[2])) { sscanf(charName+1, "%x", &code2); } else if (!hex && n >= 2 && n <= 3 && isdigit(charName[0]) && isdigit(charName[1])) { code2 = atoi(charName); if (code2 >= 256) code2 = -1; } else if (!hex && n >= 3 && n <= 5 && isdigit(charName[1])) { code2 = atoi(charName+1); if (code2 >= 256) code2 = -1; } //~ this is a kludge -- is there a standard internal encoding //~ used by all/most Type 1 fonts? if (code2 == 262) // hyphen code2 = 45; else if (code2 == 266) // emdash code2 = 208; } if (code2 >= 0) { map[code] = (Gushort)code2; if (code2 < 256) revMap[code2] = (Guchar)code; } else { map[code] = 0; } } else { map[code] = 0; } } } else { code2 = 0; // to make gcc happy //~ this is a hack to get around the fact that X won't draw //~ chars 0..31; this works when the fonts have duplicate encodings //~ for those chars for (code = 0; code < 32; ++code) { if ((charName = gfxFont->getCharName(code)) && (code2 = gfxFont->getCharCode(charName)) >= 0) { map[code] = (Gushort)code2; if (code2 < 256) revMap[code2] = (Guchar)code; } } for (code = 32; code < 256; ++code) { map[code] = (Gushort)code; revMap[code] = (Guchar)code; } } } // compute size, normalize matrix size = sqrt(mat21*mat21 + mat22*mat22); mat11 = mat11 / size; mat12 = -mat12 / size; mat21 = mat21 / size; mat22 = -mat22 / size; startSize = (int)size; // try to get a rotated font? rotated = !(mat11 > 0 && mat22 > 0 && fabs(mat11/mat22 - 1) < 0.2 && fabs(mat12) < 0.01 && fabs(mat21) < 0.01); // open X font -- if font is not found (which means the server can't // scale fonts), try progressively smaller and then larger sizes //~ This does a linear search -- it should get a list of fonts from //~ the server and pick the closest. if (rotated) sprintf(fontSize, "[%s%0.2f %s%0.2f %s%0.2f %s%0.2f]", mat11<0 ? "~" : "", fabs(mat11 * startSize), mat12<0 ? "~" : "", fabs(mat12 * startSize), mat21<0 ? "~" : "", fabs(mat21 * startSize), mat22<0 ? "~" : "", fabs(mat22 * startSize)); else sprintf(fontSize, "%d", startSize); sprintf(fontName, fontNameFmt, fontSize); xFont = XLoadQueryFont(display, fontName); if (!xFont) { for (sz = startSize; sz >= startSize/2 && sz >= 1; --sz) { sprintf(fontSize, "%d", sz); sprintf(fontName, fontNameFmt, fontSize); if ((xFont = XLoadQueryFont(display, fontName))) break; } if (!xFont) { for (sz = startSize + 1; sz < startSize + 10; ++sz) { sprintf(fontSize, "%d", sz); sprintf(fontName, fontNameFmt, fontSize); if ((xFont = XLoadQueryFont(display, fontName))) break; } if (!xFont) { sprintf(fontSize, "%d", startSize); sprintf(fontName, fontNameFmt, fontSize); error(-1, "Failed to open font: '%s'", fontName); return; } } } } XOutputFont::~XOutputFont() { if (xFont) XFreeFont(display, xFont); } //------------------------------------------------------------------------ // XOutputFontCache //------------------------------------------------------------------------ XOutputFontCache::XOutputFontCache(Display *display1) { int i; display = display1; for (i = 0; i < fontCacheSize; ++i) fonts[i] = NULL; numFonts = 0; } XOutputFontCache::~XOutputFontCache() { int i; for (i = 0; i < numFonts; ++i) delete fonts[i]; } XOutputFont *XOutputFontCache::getFont(GfxFont *gfxFont, double m11, double m12, double m21, double m22) { XOutputFont *font; int i, j; // is it the most recently used font? if (numFonts > 0 && fonts[0]->matches(gfxFont->getID(), m11, m12, m21, m22)) return fonts[0]; // is it in the cache? for (i = 1; i < numFonts; ++i) { if (fonts[i]->matches(gfxFont->getID(), m11, m12, m21, m22)) { font = fonts[i]; for (j = i; j > 0; --j) fonts[j] = fonts[j-1]; fonts[0] = font; return font; } } // make a new font font = new XOutputFont(gfxFont, m11, m12, m21, m22, display); if (!font->getXFont()) { delete font; return NULL; } // insert font in cache if (numFonts == fontCacheSize) { --numFonts; delete fonts[numFonts]; } for (j = numFonts; j > 0; --j) fonts[j] = fonts[j-1]; fonts[0] = font; ++numFonts; // return it return font; } //------------------------------------------------------------------------ // XOutputDev //------------------------------------------------------------------------ XOutputDev::XOutputDev(Display *display1, Pixmap pixmap1, Guint depth1, Colormap colormap, unsigned long paperColor) { XVisualInfo visualTempl; XVisualInfo *visualList; int nVisuals; Gulong mask; XGCValues gcValues; XColor xcolor; XColor *xcolors; int r, g, b, n, m, i; GBool ok; // get display/pixmap info display = display1; screenNum = DefaultScreen(display); pixmap = pixmap1; depth = depth1; // check for TrueColor visual trueColor = gFalse; if (depth == 0) { depth = DefaultDepth(display, screenNum); visualList = XGetVisualInfo(display, 0, &visualTempl, &nVisuals); for (i = 0; i < nVisuals; ++i) { if (visualList[i].visual == DefaultVisual(display, screenNum)) { if (visualList[i].c_class == TrueColor) { trueColor = gTrue; mask = visualList[i].red_mask; rShift = 0; while (mask && !(mask & 1)) { ++rShift; mask >>= 1; } rMul = (int)mask; mask = visualList[i].green_mask; gShift = 0; while (mask && !(mask & 1)) { ++gShift; mask >>= 1; } gMul = (int)mask; mask = visualList[i].blue_mask; bShift = 0; while (mask && !(mask & 1)) { ++bShift; mask >>= 1; } bMul = (int)mask; } break; } } XFree((XPointer)visualList); } // allocate a color cube if (!trueColor) { // set colors in private colormap if (installCmap) { for (numColors = 6; numColors >= 2; --numColors) { m = numColors * numColors * numColors; if (XAllocColorCells(display, colormap, False, NULL, 0, colors, m)) break; } if (numColors >= 2) { m = numColors * numColors * numColors; xcolors = (XColor *)gmalloc(m * sizeof(XColor)); n = 0; for (r = 0; r < numColors; ++r) { for (g = 0; g < numColors; ++g) { for (b = 0; b < numColors; ++b) { xcolors[n].pixel = colors[n]; xcolors[n].red = (r * 65535) / (numColors - 1); xcolors[n].green = (g * 65535) / (numColors - 1); xcolors[n].blue = (b * 65535) / (numColors - 1); xcolors[n].flags = DoRed | DoGreen | DoBlue; ++n; } } } XStoreColors(display, colormap, xcolors, m); gfree(xcolors); } else { numColors = 1; colors[0] = BlackPixel(display, screenNum); colors[1] = WhitePixel(display, screenNum); } // allocate colors in shared colormap } else { if (rgbCubeSize > maxRGBCube) rgbCubeSize = maxRGBCube; ok = gFalse; for (numColors = rgbCubeSize; numColors >= 2; --numColors) { ok = gTrue; n = 0; for (r = 0; r < numColors && ok; ++r) { for (g = 0; g < numColors && ok; ++g) { for (b = 0; b < numColors && ok; ++b) { if (n == 0) { colors[n++] = BlackPixel(display, screenNum); } else { xcolor.red = (r * 65535) / (numColors - 1); xcolor.green = (g * 65535) / (numColors - 1); xcolor.blue = (b * 65535) / (numColors - 1); if (XAllocColor(display, colormap, &xcolor)) colors[n++] = xcolor.pixel; else ok = gFalse; } } } } if (ok) break; XFreeColors(display, colormap, &colors[1], n-1, 0); } if (!ok) { numColors = 1; colors[0] = BlackPixel(display, screenNum); colors[1] = WhitePixel(display, screenNum); } } } // allocate GCs gcValues.foreground = BlackPixel(display, screenNum); gcValues.background = WhitePixel(display, screenNum); gcValues.line_width = 0; gcValues.line_style = LineSolid; strokeGC = XCreateGC(display, pixmap, GCForeground | GCBackground | GCLineWidth | GCLineStyle, &gcValues); fillGC = XCreateGC(display, pixmap, GCForeground | GCBackground | GCLineWidth | GCLineStyle, &gcValues); gcValues.foreground = paperColor; paperGC = XCreateGC(display, pixmap, GCForeground | GCBackground | GCLineWidth | GCLineStyle, &gcValues); // no clip region yet clipRegion = NULL; // get user font map for (n = 0; devFontMap[n].pdfFont; ++n) ; userFontMap = (FontMapEntry *)gmalloc((n+1) * sizeof(FontMapEntry)); for (i = 0; i < n; ++i) { userFontMap[i].pdfFont = devFontMap[i].pdfFont; userFontMap[i].xFont = devFontMap[i].devFont; m = strlen(userFontMap[i].xFont); if (m >= 10 && !strcmp(userFontMap[i].xFont + m - 10, "-iso8859-2")) userFontMap[i].encoding = &isoLatin2Encoding; else if (m >= 13 && !strcmp(userFontMap[i].xFont + m - 13, "-fontspecific")) userFontMap[i].encoding = NULL; else userFontMap[i].encoding = &isoLatin1Encoding; } userFontMap[n].pdfFont = NULL; // set up the font cache and fonts gfxFont = NULL; font = NULL; fontCache = new XOutputFontCache(display); // empty state stack save = NULL; // create text object text = new TextPage(gFalse); } XOutputDev::~XOutputDev() { gfree(userFontMap); delete fontCache; XFreeGC(display, strokeGC); XFreeGC(display, fillGC); XFreeGC(display, paperGC); if (clipRegion) XDestroyRegion(clipRegion); delete text; } void XOutputDev::startPage(int pageNum, GfxState *state) { XOutputState *s; XGCValues gcValues; XRectangle rect; // clear state stack while (save) { s = save; save = save->next; XFreeGC(display, s->strokeGC); XFreeGC(display, s->fillGC); XDestroyRegion(s->clipRegion); delete s; } save = NULL; // default line flatness flatness = 0; // reset GCs gcValues.foreground = BlackPixel(display, screenNum); gcValues.background = WhitePixel(display, screenNum); gcValues.line_width = 0; gcValues.line_style = LineSolid; XChangeGC(display, strokeGC, GCForeground | GCBackground | GCLineWidth | GCLineStyle, &gcValues); XChangeGC(display, fillGC, GCForeground | GCBackground | GCLineWidth | GCLineStyle, &gcValues); // clear clipping region if (clipRegion) XDestroyRegion(clipRegion); clipRegion = XCreateRegion(); rect.x = rect.y = 0; rect.width = pixmapW; rect.height = pixmapH; XUnionRectWithRegion(&rect, clipRegion, clipRegion); XSetRegion(display, strokeGC, clipRegion); XSetRegion(display, fillGC, clipRegion); // clear font gfxFont = NULL; font = NULL; // clear window XFillRectangle(display, pixmap, paperGC, 0, 0, pixmapW, pixmapH); // clear text object text->clear(); } void XOutputDev::endPage() { text->coalesce(); } void XOutputDev::drawLinkBorder(double x1, double y1, double x2, double y2, double w) { GfxColor color; XPoint points[5]; int x, y; color.setRGB(0, 0, 1); XSetForeground(display, strokeGC, findColor(&color)); XSetLineAttributes(display, strokeGC, xoutRound(w), LineSolid, CapRound, JoinRound); cvtUserToDev(x1, y1, &x, &y); points[0].x = points[4].x = x; points[0].y = points[4].y = y; cvtUserToDev(x2, y1, &x, &y); points[1].x = x; points[1].y = y; cvtUserToDev(x2, y2, &x, &y); points[2].x = x; points[2].y = y; cvtUserToDev(x1, y2, &x, &y); points[3].x = x; points[3].y = y; XDrawLines(display, pixmap, strokeGC, points, 5, CoordModeOrigin); } void XOutputDev::saveState(GfxState *state) { XOutputState *s; XGCValues values; // save current state s = new XOutputState; s->strokeGC = strokeGC; s->fillGC = fillGC; s->clipRegion = clipRegion; // push onto state stack s->next = save; save = s; // create a new current state by copying strokeGC = XCreateGC(display, pixmap, 0, &values); XCopyGC(display, s->strokeGC, 0xffffffff, strokeGC); fillGC = XCreateGC(display, pixmap, 0, &values); XCopyGC(display, s->fillGC, 0xffffffff, fillGC); clipRegion = XCreateRegion(); XUnionRegion(s->clipRegion, clipRegion, clipRegion); XSetRegion(display, strokeGC, clipRegion); XSetRegion(display, fillGC, clipRegion); } void XOutputDev::restoreState(GfxState *state) { XOutputState *s; if (save) { // kill current state XFreeGC(display, strokeGC); XFreeGC(display, fillGC); XDestroyRegion(clipRegion); // restore state flatness = state->getFlatness(); strokeGC = save->strokeGC; fillGC = save->fillGC; clipRegion = save->clipRegion; XSetRegion(display, strokeGC, clipRegion); XSetRegion(display, fillGC, clipRegion); // pop state stack s = save; save = save->next; delete s; } } void XOutputDev::updateAll(GfxState *state) { updateLineAttrs(state, gTrue); updateFlatness(state); updateMiterLimit(state); updateFillColor(state); updateStrokeColor(state); updateFont(state); } void XOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) { updateLineAttrs(state, gTrue); } void XOutputDev::updateLineDash(GfxState *state) { updateLineAttrs(state, gTrue); } void XOutputDev::updateFlatness(GfxState *state) { flatness = state->getFlatness(); } void XOutputDev::updateLineJoin(GfxState *state) { updateLineAttrs(state, gFalse); } void XOutputDev::updateLineCap(GfxState *state) { updateLineAttrs(state, gFalse); } // unimplemented void XOutputDev::updateMiterLimit(GfxState *state) { } void XOutputDev::updateLineWidth(GfxState *state) { updateLineAttrs(state, gFalse); } void XOutputDev::updateLineAttrs(GfxState *state, GBool updateDash) { double width; int cap, join; double *dashPattern; int dashLength; double dashStart; char dashList[20]; int i; width = state->getTransformedLineWidth(); switch (state->getLineCap()) { case 0: cap = CapButt; break; case 1: cap = CapRound; break; case 2: cap = CapProjecting; break; default: error(-1, "Bad line cap style (%d)", state->getLineCap()); cap = CapButt; break; } switch (state->getLineJoin()) { case 0: join = JoinMiter; break; case 1: join = JoinRound; break; case 2: join = JoinBevel; break; default: error(-1, "Bad line join style (%d)", state->getLineJoin()); join = JoinMiter; break; } state->getLineDash(&dashPattern, &dashLength, &dashStart); XSetLineAttributes(display, strokeGC, xoutRound(width), dashLength > 0 ? LineOnOffDash : LineSolid, cap, join); if (updateDash && dashLength > 0) { if (dashLength > 20) dashLength = 20; for (i = 0; i < dashLength; ++i) { dashList[i] = xoutRound(state->transformWidth(dashPattern[i])); if (dashList[i] == 0) dashList[i] = 1; } XSetDashes(display, strokeGC, xoutRound(dashStart), dashList, dashLength); } } void XOutputDev::updateFillColor(GfxState *state) { XSetForeground(display, fillGC, findColor(state->getFillColor())); } void XOutputDev::updateStrokeColor(GfxState *state) { XSetForeground(display, strokeGC, findColor(state->getStrokeColor())); } void XOutputDev::updateFont(GfxState *state) { double m11, m12, m21, m22; if (!(gfxFont = state->getFont())) { font = NULL; return; } state->getFontTransMat(&m11, &m12, &m21, &m22); m11 *= state->getHorizScaling(); m21 *= state->getHorizScaling(); font = fontCache->getFont(gfxFont, m11, m12, m21, m22); if (font) { XSetFont(display, fillGC, font->getXFont()->fid); XSetFont(display, strokeGC, font->getXFont()->fid); } } void XOutputDev::stroke(GfxState *state) { XPoint *points; int *lengths; int n, size, numPoints, i, j; // transform points n = convertPath(state, &points, &size, &numPoints, &lengths, gFalse); // draw each subpath j = 0; for (i = 0; i < n; ++i) { XDrawLines(display, pixmap, strokeGC, points + j, lengths[i], CoordModeOrigin); j += lengths[i]; } // free points and lengths arrays if (points != tmpPoints) gfree(points); if (lengths != tmpLengths) gfree(lengths); } void XOutputDev::fill(GfxState *state) { doFill(state, WindingRule); } void XOutputDev::eoFill(GfxState *state) { doFill(state, EvenOddRule); } // // X doesn't color the pixels on the right-most and bottom-most // borders of a polygon. This means that one-pixel-thick polygons // are not colored at all. I think this is supposed to be a // feature, but I can't figure out why. So after it fills a // polygon, it also draws lines around the border. This is done // only for single-component polygons, since it's not very // compatible with the compound polygon kludge (see convertPath()). // void XOutputDev::doFill(GfxState *state, int rule) { XPoint *points; int *lengths; int n, size, numPoints, i, j; // set fill rule XSetFillRule(display, fillGC, rule); // transform points, build separate polygons n = convertPath(state, &points, &size, &numPoints, &lengths, gTrue); // fill them j = 0; for (i = 0; i < n; ++i) { XFillPolygon(display, pixmap, fillGC, points + j, lengths[i], Complex, CoordModeOrigin); if (state->getPath()->getNumSubpaths() == 1) { XDrawLines(display, pixmap, fillGC, points + j, lengths[i], CoordModeOrigin); } j += lengths[i] + 1; } // free points and lengths arrays if (points != tmpPoints) gfree(points); if (lengths != tmpLengths) gfree(lengths); } void XOutputDev::clip(GfxState *state) { doClip(state, WindingRule); } void XOutputDev::eoClip(GfxState *state) { doClip(state, EvenOddRule); } void XOutputDev::doClip(GfxState *state, int rule) { Region region, region2; XPoint *points; int *lengths; int n, size, numPoints, i, j; // transform points, build separate polygons n = convertPath(state, &points, &size, &numPoints, &lengths, gTrue); // construct union of subpath regions region = XPolygonRegion(points, lengths[0], rule); j = lengths[0] + 1; for (i = 1; i < n; ++i) { region2 = XPolygonRegion(points + j, lengths[i], rule); XUnionRegion(region2, region, region); XDestroyRegion(region2); j += lengths[i] + 1; } // intersect region with clipping region XIntersectRegion(region, clipRegion, clipRegion); XDestroyRegion(region); XSetRegion(display, strokeGC, clipRegion); XSetRegion(display, fillGC, clipRegion); // free points and lengths arrays if (points != tmpPoints) gfree(points); if (lengths != tmpLengths) gfree(lengths); } // // Transform points in the path and convert curves to line segments. // Builds a set of subpaths and returns the number of subpaths. // If is set, close any unclosed subpaths and activate a // kludge for polygon fills: First, it divides up the subpaths into // non-overlapping polygons by simply comparing bounding rectangles. // Then it connects subaths within a single compound polygon to a single // point so that X can fill the polygon (sort of). // int XOutputDev::convertPath(GfxState *state, XPoint **points, int *size, int *numPoints, int **lengths, GBool fillHack) { GfxPath *path; BoundingRect *rects; BoundingRect rect; int n, i, ii, j, k, k0; // get path and number of subpaths path = state->getPath(); n = path->getNumSubpaths(); // allocate lengths array if (n < numTmpSubpaths) *lengths = tmpLengths; else *lengths = (int *)gmalloc(n * sizeof(int)); // allocate bounding rectangles array if (fillHack) { if (n < numTmpSubpaths) rects = tmpRects; else rects = (BoundingRect *)gmalloc(n * sizeof(BoundingRect)); } else { rects = NULL; } // do each subpath *points = tmpPoints; *size = numTmpPoints; *numPoints = 0; for (i = 0; i < n; ++i) { // transform the points j = *numPoints; convertSubpath(state, path->getSubpath(i), points, size, numPoints); // construct bounding rectangle if (fillHack) { rects[i].xMin = rects[i].xMax = (*points)[j].x; rects[i].yMin = rects[i].yMax = (*points)[j].y; for (k = j + 1; k < *numPoints; ++k) { if ((*points)[k].x < rects[i].xMin) rects[i].xMin = (*points)[k].x; else if ((*points)[k].x > rects[i].xMax) rects[i].xMax = (*points)[k].x; if ((*points)[k].y < rects[i].yMin) rects[i].yMin = (*points)[k].y; else if ((*points)[k].y > rects[i].yMax) rects[i].yMax = (*points)[k].y; } } // close subpath if necessary if (fillHack && ((*points)[*numPoints-1].x != (*points)[j].x || (*points)[*numPoints-1].y != (*points)[j].y)) { addPoint(points, size, numPoints, (*points)[j].x, (*points)[j].y); } // length of this subpath (*lengths)[i] = *numPoints - j; // leave an extra point for compound fill hack if (fillHack) addPoint(points, size, numPoints, 0, 0); } // combine compound polygons if (fillHack) { i = j = k = 0; while (i < n) { // start with subpath i rect = rects[i]; (*lengths)[j] = (*lengths)[i]; k0 = k; (*points)[k + (*lengths)[i]] = (*points)[k0]; k += (*lengths)[i] + 1; ++i; // combine overlapping polygons do { // look for the first subsequent subpath, if any, which overlaps for (ii = i; ii < n; ++ii) { if (((rects[ii].xMin > rect.xMin && rects[ii].xMin < rect.xMax) || (rects[ii].xMax > rect.xMin && rects[ii].xMax < rect.xMax) || (rects[ii].xMin < rect.xMin && rects[ii].xMax > rect.xMax)) && ((rects[ii].yMin > rect.yMin && rects[ii].yMin < rect.yMax) || (rects[ii].yMax > rect.yMin && rects[ii].yMax < rect.yMax) || (rects[ii].yMin < rect.yMin && rects[ii].yMax > rect.yMax))) break; } // if there is an overlap, combine the polygons if (ii < n) { for (; i <= ii; ++i) { if (rects[i].xMin < rect.xMin) rect.xMin = rects[j].xMin; if (rects[i].xMax > rect.xMax) rect.xMax = rects[j].xMax; if (rects[i].yMin < rect.yMin) rect.yMin = rects[j].yMin; if (rects[i].yMax > rect.yMax) rect.yMax = rects[j].yMax; (*lengths)[j] += (*lengths)[i] + 1; (*points)[k + (*lengths)[i]] = (*points)[k0]; k += (*lengths)[i] + 1; } } } while (ii < n && i < n); ++j; } // free bounding rectangles if (rects != tmpRects) gfree(rects); n = j; } return n; } // // Transform points in a single subpath and convert curves to line // segments. // void XOutputDev::convertSubpath(GfxState *state, GfxSubpath *subpath, XPoint **points, int *size, int *n) { double x0, y0, x1, y1, x2, y2, x3, y3; int m, i; m = subpath->getNumPoints(); i = 0; while (i < m) { if (i >= 1 && subpath->getCurve(i)) { state->transform(subpath->getX(i-1), subpath->getY(i-1), &x0, &y0); state->transform(subpath->getX(i), subpath->getY(i), &x1, &y1); state->transform(subpath->getX(i+1), subpath->getY(i+1), &x2, &y2); state->transform(subpath->getX(i+2), subpath->getY(i+2), &x3, &y3); doCurve(points, size, n, x0, y0, x1, y1, x2, y2, x3, y3); i += 3; } else { state->transform(subpath->getX(i), subpath->getY(i), &x1, &y1); addPoint(points, size, n, xoutRound(x1), xoutRound(y1)); ++i; } } } // // Subdivide a Bezier curve. This uses floating point to avoid // propagating rounding errors. (The curves look noticeably more // jagged with integer arithmetic.) // void XOutputDev::doCurve(XPoint **points, int *size, int *n, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) { double x[(1<= *size) { *size += 32; if (*points == tmpPoints) { *points = (XPoint *)gmalloc(*size * sizeof(XPoint)); memcpy(*points, tmpPoints, *k * sizeof(XPoint)); } else { *points = (XPoint *)grealloc(*points, *size * sizeof(XPoint)); } } (*points)[*k].x = x; (*points)[*k].y = y; ++(*k); } void XOutputDev::beginString(GfxState *state, GString *s) { text->beginString(state, s, font ? font->isHex() : gFalse); } void XOutputDev::endString(GfxState *state) { text->endString(); } void XOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, Guchar c) { Gushort c1; char buf; char *p; int n, i; double x1, y1; double tx; text->addChar(state, x, y, dx, dy, c); if (!font) return; // check for invisible text -- this is used by Acrobat Capture if ((state->getRender() & 3) == 3) return; state->transform(x, y, &x1, &y1); c1 = font->mapChar(c); if (c1 <= lastRegularChar) { buf = (char)c1; XDrawString(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), &buf, 1); } else if (c1 <= lastSubstChar) { buf = (char)substChars[c1 - firstSubstChar]; XDrawString(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), &buf, 1); } else if (c1 <= lastConstrChar) { //~ need to deal with rotated text here switch (c1 - firstConstrChar) { case 0: // bullet tx = 0.25 * state->getTransformedFontSize() * gfxFont->getWidth(c); XFillRectangle(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1 + tx), xoutRound(y1 - 0.4 * font->getXFont()->ascent - tx), xoutRound(2 * tx), xoutRound(2 * tx)); break; case 1: // trademark //~ this should use a smaller font // tx = state->getTransformedFontSize() * // (gfxFont->getWidth(c) - // gfxFont->getWidth(font->revCharMap('M'))); tx = 0.9 * state->getTransformedFontSize() * gfxFont->getWidth(font->revMapChar('T')); y1 -= 0.33 * (double)font->getXFont()->ascent; buf = 'T'; XDrawString(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), &buf, 1); x1 += tx; buf = 'M'; XDrawString(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), &buf, 1); break; } } else if (c1 <= lastMultiChar) { p = multiChars[c1 - firstMultiChar]; n = strlen(p); tx = gfxFont->getWidth(c); tx -= gfxFont->getWidth(font->revMapChar(p[n-1])); tx = tx * state->getTransformedFontSize() / (double)(n - 1); for (i = 0; i < n; ++i) { XDrawString(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), p + i, 1); x1 += tx; } } } void XOutputDev::drawChar16(GfxState *state, double x, double y, double dx, double dy, int c) { int c1; XChar2b c2[4]; double x1, y1; #if JAPANESE_SUPPORT int t1, t2; double x2; char *p; int n, i; #endif if (!font) return; // check for invisible text -- this is used by Acrobat Capture if ((state->getRender() & 3) == 3) return; state->transform(x, y, &x1, &y1); c1 = 0; switch (gfxFont->getCharSet16()) { // convert Adobe-Japan1-2 to JIS X 0208-1983 case font16AdobeJapan12: #if JAPANESE_SUPPORT if (c <= 96) { c1 = japan12Map[c]; } else if (c <= 632) { if (c <= 230) c1 = 0; else if (c <= 324) c1 = japan12Map[c - 230]; else if (c <= 421) c1 = japan12KanaMap1[c - 325]; else if (c <= 500) c1 = 0; else if (c <= 598) c1 = japan12KanaMap2[c - 501]; else c1 = 0; } else if (c <= 1124) { if (c <= 779) { if (c <= 726) c1 = 0x2121 + (c - 633); else if (c <= 740) c1 = 0x2221 + (c - 727); else if (c <= 748) c1 = 0x223a + (c - 741); else if (c <= 755) c1 = 0x224a + (c - 749); else if (c <= 770) c1 = 0x225c + (c - 756); else if (c <= 778) c1 = 0x2272 + (c - 771); else c1 = 0x227e; } else if (c <= 841) { if (c <= 789) c1 = 0x2330 + (c - 780); else if (c <= 815) c1 = 0x2341 + (c - 790); else c1 = 0x2361 + (c - 816); } else if (c <= 1010) { if (c <= 924) c1 = 0x2421 + (c - 842); else c1 = 0x2521 + (c - 925); } else { if (c <= 1034) c1 = 0x2621 + (c - 1011); else if (c <= 1058) c1 = 0x2641 + (c - 1035); else if (c <= 1091) c1 = 0x2721 + (c - 1059); else c1 = 0x2751 + (c - 1092); } } else if (c <= 4089) { t1 = (c - 1125) / 94; t2 = (c - 1125) % 94; c1 = 0x3021 + (t1 << 8) + t2; } else if (c <= 7477) { t1 = (c - 4090) / 94; t2 = (c - 4090) % 94; c1 = 0x5021 + (t1 << 8) + t2; } else if (c <= 7554) { c1 = 0; } else if (c <= 7563) { // circled Arabic numbers 1..9 c1 = 0x2331 + (c - 7555); c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), c2, 1); c1 = 0x227e; c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), c2, 1); c1 = -1; } else if (c <= 7574) { // circled Arabic numbers 10..20 n = c - 7564 + 10; x2 = x1; for (i = 0; i < 2; ++i) { c1 = 0x2330 + (i == 0 ? (n / 10) : (n % 10)); c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x2), xoutRound(y1), c2, 1); x2 += 0.5 * state->getTransformedFontSize(); } c1 = 0x227e; c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), c2, 1); c1 = -1; } else if (c <= 7584) { // Roman numbers I..X p = japan12Roman[c - 7575]; n = strlen(p); for (; *p; ++p) { if (*p == 'I') c1 = 0x2349; else if (*p == 'V') c1 = 0x2356; else // 'X' c1 = 0x2358; c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), c2, 1); if (*p == 'I') x1 += 0.2 * state->getTransformedFontSize(); else x1 += 0.5 * state->getTransformedFontSize(); } c1 = -1; } else if (c <= 7632) { if (c <= 7600) { c1 = 0; } else if (c <= 7606) { p = japan12Abbrev1[c - 7601]; n = strlen(p); for (; *p; ++p) { c1 = 0x2300 + *p; c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), c2, 1); x1 += 0.5 * state->getTransformedFontSize(); } c1 = -1; } else { c1 = 0; } } else { c1 = 0; } #if 0 //~ if (c1 == 0) error(-1, "Unsupported Adobe-Japan1-2 character: %d", c); #endif #endif // JAPANESE_SUPPORT break; } if (c1 > 0) { c2[0].byte1 = c1 >> 8; c2[0].byte2 = c1 & 0xff; XDrawString16(display, pixmap, (state->getRender() & 1) ? strokeGC : fillGC, xoutRound(x1), xoutRound(y1), c2, 1); } } void XOutputDev::drawImageMask(GfxState *state, Stream *str, int width, int height, GBool invert, GBool inlineImg) { XImage *image; int x0, y0; // top left corner of image int w0, h0, w1, h1; // size of image int x2, y2; int w2, h2; double xt, yt, wt, ht; GBool rotate, xFlip, yFlip; int x, y; int ix, iy; int px1, px2, qx, dx; int py1, py2, qy, dy; Guchar pixBuf; Gulong color; int i, j; // get image position and size state->transform(0, 0, &xt, &yt); state->transformDelta(1, 1, &wt, &ht); if (wt > 0) { x0 = xoutRound(xt); w0 = xoutRound(wt); } else { x0 = xoutRound(xt + wt); w0 = xoutRound(-wt); } if (ht > 0) { y0 = xoutRound(yt); h0 = xoutRound(ht); } else { y0 = xoutRound(yt + ht); h0 = xoutRound(-ht); } state->transformDelta(1, 0, &xt, &yt); rotate = fabs(xt) < fabs(yt); if (rotate) { w1 = h0; h1 = w0; xFlip = ht < 0; yFlip = wt > 0; } else { w1 = w0; h1 = h0; xFlip = wt < 0; yFlip = ht > 0; } // set up color = findColor(state->getFillColor()); // check for tiny (zero width or height) images if (w0 == 0 || h0 == 0) { j = height * ((width + 7) / 8); str->reset(); for (i = 0; i < j; ++i) str->getChar(); return; } // Bresenham parameters px1 = w1 / width; px2 = w1 - px1 * width; py1 = h1 / height; py2 = h1 - py1 * height; // allocate XImage image = XCreateImage(display, DefaultVisual(display, screenNum), depth, ZPixmap, 0, NULL, w0, h0, 8, 0); image->data = (char *)gmalloc(h0 * image->bytes_per_line); if (x0 + w0 > pixmapW) w2 = pixmapW - x0; else w2 = w0; if (x0 < 0) { x2 = -x0; w2 += x0; x0 = 0; } else { x2 = 0; } if (y0 + h0 > pixmapH) h2 = pixmapH - y0; else h2 = h0; if (y0 < 0) { y2 = -y0; h2 += y0; y0 = 0; } else { y2 = 0; } XGetSubImage(display, pixmap, x0, y0, w2, h2, (1 << depth) - 1, ZPixmap, image, x2, y2); // initialize the image stream str->resetImage(width, 1, 1); // first line (column) y = yFlip ? h1 - 1 : 0; qy = 0; // read image for (i = 0; i < height; ++i) { // vertical (horizontal) Bresenham dy = py1; if ((qy += py2) >= height) { ++dy; qy -= height; } // drop a line (column) if (dy == 0) { str->skipImageLine(); } else { // first column (line) x = xFlip ? w1 - 1 : 0; qx = 0; // for each column (line)... for (j = 0; j < width; ++j) { // horizontal (vertical) Bresenham dx = px1; if ((qx += px2) >= width) { ++dx; qx -= width; } // get image pixel str->getImagePixel(&pixBuf); if (invert) pixBuf ^= 1; // draw image pixel if (dx > 0 && pixBuf == 0) { if (dx == 1 && dy == 1) { if (rotate) XPutPixel(image, y, x, color); else XPutPixel(image, x, y, color); } else { for (ix = 0; ix < dx; ++ix) { for (iy = 0; iy < dy; ++iy) { if (rotate) XPutPixel(image, yFlip ? y - iy : y + iy, xFlip ? x - ix : x + ix, color); else XPutPixel(image, xFlip ? x - ix : x + ix, yFlip ? y - iy : y + iy, color); } } } } // next column (line) if (xFlip) x -= dx; else x += dx; } } // next line (column) if (yFlip) y -= dy; else y += dy; } // blit the image into the pixmap XPutImage(display, pixmap, fillGC, image, x2, y2, x0, y0, w2, h2); // free memory gfree(image->data); image->data = NULL; XDestroyImage(image); } inline Gulong XOutputDev::findColor(RGBColor *x, RGBColor *err) { double gray; int r, g, b; Gulong pixel; if (trueColor) { r = xoutRound(x->r * rMul); g = xoutRound(x->g * gMul); b = xoutRound(x->b * bMul); pixel = ((Gulong)r << rShift) + ((Gulong)g << gShift) + ((Gulong)b << bShift); err->r = x->r - (double)r / rMul; err->g = x->g - (double)g / gMul; err->b = x->b - (double)b / bMul; } else if (numColors == 1) { gray = 0.299 * x->r + 0.587 * x->g + 0.114 * x->b; if (gray < 0.5) { pixel = colors[0]; err->r = x->r; err->g = x->g; err->b = x->b; } else { pixel = colors[1]; err->r = x->r - 1; err->g = x->g - 1; err->b = x->b - 1; } } else { r = xoutRound(x->r * (numColors - 1)); g = xoutRound(x->g * (numColors - 1)); b = xoutRound(x->b * (numColors - 1)); pixel = colors[(r * numColors + g) * numColors + b]; err->r = x->r - (double)r / (numColors - 1); err->g = x->g - (double)g / (numColors - 1); err->b = x->b - (double)b / (numColors - 1); } return pixel; } void XOutputDev::drawImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *colorMap, GBool inlineImg) { XImage *image; int x0, y0; // top left corner of image int w0, h0, w1, h1; // size of image double xt, yt, wt, ht; GBool rotate, xFlip, yFlip; GBool dither; int x, y; int ix, iy; int px1, px2, qx, dx; int py1, py2, qy, dy; Guchar pixBuf[4]; Gulong pixel; int nComps, nVals, nBits; double r1, g1, b1; GfxColor color; RGBColor color2, err; RGBColor *errRight, *errDown; int i, j; // get image position and size state->transform(0, 0, &xt, &yt); state->transformDelta(1, 1, &wt, &ht); if (wt > 0) { x0 = xoutRound(xt); w0 = xoutRound(wt); } else { x0 = xoutRound(xt + wt); w0 = xoutRound(-wt); } if (ht > 0) { y0 = xoutRound(yt); h0 = xoutRound(ht); } else { y0 = xoutRound(yt + ht); h0 = xoutRound(-ht); } state->transformDelta(1, 0, &xt, &yt); rotate = fabs(xt) < fabs(yt); if (rotate) { w1 = h0; h1 = w0; xFlip = ht < 0; yFlip = wt > 0; } else { w1 = w0; h1 = h0; xFlip = wt < 0; yFlip = ht > 0; } // set up nComps = colorMap->getNumPixelComps(); nVals = width * nComps; nBits = colorMap->getBits(); dither = nComps > 1 || nBits > 1; // check for tiny (zero width or height) images if (w0 == 0 || h0 == 0) { j = height * ((nVals * nBits + 7) / 8); str->reset(); for (i = 0; i < j; ++i) str->getChar(); return; } // Bresenham parameters px1 = w1 / width; px2 = w1 - px1 * width; py1 = h1 / height; py2 = h1 - py1 * height; // allocate XImage image = XCreateImage(display, DefaultVisual(display, screenNum), depth, ZPixmap, 0, NULL, w0, h0, 8, 0); image->data = (char *)gmalloc(h0 * image->bytes_per_line); // allocate error diffusion accumulators if (dither) { errDown = (RGBColor *)gmalloc(w1 * sizeof(RGBColor)); errRight = (RGBColor *)gmalloc((py1 + 1) * sizeof(RGBColor)); for (j = 0; j < w1; ++j) errDown[j].r = errDown[j].g = errDown[j].b = 0; } else { errDown = NULL; errRight = NULL; } // initialize the image stream str->resetImage(width, nComps, nBits); // first line (column) y = yFlip ? h1 - 1 : 0; qy = 0; // read image for (i = 0; i < height; ++i) { // vertical (horizontal) Bresenham dy = py1; if ((qy += py2) >= height) { ++dy; qy -= height; } // drop a line (column) if (dy == 0) { str->skipImageLine(); } else { // first column (line) x = xFlip ? w1 - 1 : 0; qx = 0; // clear error accumulator if (dither) { for (j = 0; j <= py1; ++j) errRight[j].r = errRight[j].g = errRight[j].b = 0; } // for each column (line)... for (j = 0; j < width; ++j) { // horizontal (vertical) Bresenham dx = px1; if ((qx += px2) >= width) { ++dx; qx -= width; } // get image pixel str->getImagePixel(pixBuf); // draw image pixel if (dx > 0) { colorMap->getColor(pixBuf, &color); r1 = color.getR(); g1 = color.getG(); b1 = color.getB(); if (dither) { pixel = 0; } else { color2.r = r1; color2.g = g1; color2.b = b1; pixel = findColor(&color2, &err); } if (dx == 1 && dy == 1) { if (dither) { color2.r = r1 + errRight[0].r + errDown[x].r; if (color2.r > 1) color2.r = 1; else if (color2.r < 0) color2.r = 0; color2.g = g1 + errRight[0].g + errDown[x].g; if (color2.g > 1) color2.g = 1; else if (color2.g < 0) color2.g = 0; color2.b = b1 + errRight[0].b + errDown[x].b; if (color2.b > 1) color2.b = 1; else if (color2.b < 0) color2.b = 0; pixel = findColor(&color2, &err); errRight[0].r = errDown[x].r = err.r / 2; errRight[0].g = errDown[x].g = err.g / 2; errRight[0].b = errDown[x].b = err.b / 2; } if (rotate) XPutPixel(image, y, x, pixel); else XPutPixel(image, x, y, pixel); } else { for (iy = 0; iy < dy; ++iy) { for (ix = 0; ix < dx; ++ix) { if (dither) { color2.r = r1 + errRight[iy].r + errDown[xFlip ? x - ix : x + ix].r; if (color2.r > 1) color2.r = 1; else if (color2.r < 0) color2.r = 0; color2.g = g1 + errRight[iy].g + errDown[xFlip ? x - ix : x + ix].g; if (color2.g > 1) color2.g = 1; else if (color2.g < 0) color2.g = 0; color2.b = b1 + errRight[iy].b + errDown[xFlip ? x - ix : x + ix].b; if (color2.b > 1) color2.b = 1; else if (color2.b < 0) color2.b = 0; pixel = findColor(&color2, &err); errRight[iy].r = errDown[xFlip ? x - ix : x + ix].r = err.r / 2; errRight[iy].g = errDown[xFlip ? x - ix : x + ix].g = err.g / 2; errRight[iy].b = errDown[xFlip ? x - ix : x + ix].b = err.b / 2; } if (rotate) XPutPixel(image, yFlip ? y - iy : y + iy, xFlip ? x - ix : x + ix, pixel); else XPutPixel(image, xFlip ? x - ix : x + ix, yFlip ? y - iy : y + iy, pixel); } } } } // next column (line) if (xFlip) x -= dx; else x += dx; } } // next line (column) if (yFlip) y -= dy; else y += dy; } // blit the image into the pixmap XPutImage(display, pixmap, fillGC, image, 0, 0, x0, y0, w0, h0); // free memory gfree(image->data); image->data = NULL; XDestroyImage(image); gfree(errRight); gfree(errDown); } Gulong XOutputDev::findColor(GfxColor *color) { int r, g, b; double gray; Gulong pixel; if (trueColor) { r = xoutRound(color->getR() * rMul); g = xoutRound(color->getG() * gMul); b = xoutRound(color->getB() * bMul); pixel = ((Gulong)r << rShift) + ((Gulong)g << gShift) + ((Gulong)b << bShift); } else if (numColors == 1) { gray = color->getGray(); if (gray < 0.5) pixel = colors[0]; else pixel = colors[1]; } else { r = xoutRound(color->getR() * (numColors - 1)); g = xoutRound(color->getG() * (numColors - 1)); b = xoutRound(color->getB() * (numColors - 1)); #if 0 //~ this makes things worse as often as better // even a very light color shouldn't map to white if (r == numColors - 1 && g == numColors - 1 && b == numColors - 1) { if (color->getR() < 0.95) --r; if (color->getG() < 0.95) --g; if (color->getB() < 0.95) --b; } #endif pixel = colors[(r * numColors + g) * numColors + b]; } return pixel; } GBool XOutputDev::findText(char *s, GBool top, GBool bottom, int *xMin, int *yMin, int *xMax, int *yMax) { double xMin1, yMin1, xMax1, yMax1; xMin1 = (double)*xMin; yMin1 = (double)*yMin; xMax1 = (double)*xMax; yMax1 = (double)*yMax; if (text->findText(s, top, bottom, &xMin1, &yMin1, &xMax1, &yMax1)) { *xMin = xoutRound(xMin1); *xMax = xoutRound(xMax1); *yMin = xoutRound(yMin1); *yMax = xoutRound(yMax1); return gTrue; } return gFalse; } GString *XOutputDev::getText(int xMin, int yMin, int xMax, int yMax) { return text->getText((double)xMin, (double)yMin, (double)xMax, (double)yMax); }