// -------------------------------------------------------------------- // Canvas Painter // -------------------------------------------------------------------- /* This file is part of the extensible drawing editor Ipe. Copyright (C) 1993-2004 Otfried Cheong Ipe is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. As a special exception, you have permission to link Ipe with the CGAL library and distribute executables, as long as you follow the requirements of the Gnu General Public License in regard to all of the software in the executable aside from CGAL. Ipe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Ipe; if not, you can find it at "http://www.gnu.org/copyleft/gpl.html", or write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ipetext.h" #include "ipepdfparser.h" #include "ipecanvas.h" #include "ipefonts.h" #include #include #include #include #include inline QPoint QPt(const IpeVector &v) { return QPoint(int(v.iX), int(v.iY)); } inline IpeString IpeQ(const QString &str) { return IpeString(str.utf8()); } inline QString QIpe(const IpeString &str) { return QString::fromUtf8(str.CString()); } // -------------------------------------------------------------------- class IpeRenderData : public IpeBitmap::MRenderData { public: virtual ~IpeRenderData() { /* nothing */ } QImage iImage; }; // -------------------------------------------------------------------- IpeCanvasPainter::IpeCanvasPainter(const IpeStyleSheet *sheet, IpeCanvasFonts *fonts, QPixmap *pixmap, QPainter *painter, double zoom, bool pretty, int maxBitmapSize) : IpePainter(sheet), iFonts(fonts), iPixmap(pixmap), iPainter(painter), iZoom(zoom), iPretty(pretty), iMaxBitmapSize(maxBitmapSize) { iDimmed = false; iPainter->setFont(QApplication::font()); iFont = 0; // these symbolic dash styles are on fixed indices for (int i = 0; i < 4; ++i) iDash[i] = StyleSheet()->Find(IpeAttribute(IpeAttribute::EDashStyle, true, i+1)); } void IpeCanvasPainter::DoMoveTo(const IpeVector &v) { if (iType.size() > 0 && iType.back() != EEndClosedPath) iType.push_back(EEndPath); iV.push_back(QPt(Matrix() * v)); iType.push_back(EMoveTo); } void IpeCanvasPainter::DoLineTo(const IpeVector &v) { iV.push_back(QPt(Matrix() * v)); iType.push_back(ELineTo); } void IpeCanvasPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2, const IpeVector &v3) { iV.push_back(QPt(Matrix() * v1)); iV.push_back(QPt(Matrix() * v2)); iV.push_back(QPt(Matrix() * v3)); iType.push_back(ECurveTo); } void IpeCanvasPainter::DoClosePath() { iType.push_back(EEndClosedPath); } void IpeCanvasPainter::DoDrawPath() { if (iType.size() > 0 && iType.back() != EEndClosedPath) iType.push_back(EEndPath); const IpeRepository *rep = StyleSheet()->Repository(); // draw interior IpeAttribute fill = Fill(); if (!fill.IsNullOrVoid()) { // fill color must be absolute assert(fill.IsAbsolute()); IpeColor fillColor = rep->ToColor(fill); QColor qfill(int(fillColor.iRed * 255), int(fillColor.iGreen * 255), int(fillColor.iBlue * 255)); QBrush brush; brush.setColor(iDimmed ? qfill.light() : qfill); brush.setStyle(QBrush::SolidPattern); /* if (qfill.red() > qfill.green()) brush.setStyle(QBrush::Dense4Pattern); else brush.setStyle(QBrush::CrossPattern); */ iPainter->setBrush(brush); iPainter->setPen(QPainter::NoPen); QPoint org; std::vector pv; pv.reserve(iV.size()); uint k = 0; QPoint start = iV[0]; for (uint i = 0; i < iType.size(); ++i) { switch (iType[i]) { case EMoveTo: org = iV[k]; // fall through case ELineTo: pv.push_back(iV[k++]); break; case ECurveTo: { QPointArray ctrlPts(4); ctrlPts.setPoint(0, iV[k-1]); ctrlPts.setPoint(1, iV[k++]); ctrlPts.setPoint(2, iV[k++]); ctrlPts.setPoint(3, iV[k++]); #if QT_VERSION >= 0x030000 QPointArray arr = ctrlPts.cubicBezier(); #else QPointArray arr = ctrlPts.quadBezier(); #endif std::copy(arr.begin(), arr.end(), std::back_inserter(pv)); } break; case EEndPath: case EEndClosedPath: pv.push_back(org); if (org != start) pv.push_back(start); break; // nothing } } QPointArray arr; int n = pv.size(); arr.setRawData(&pv[0], n); // Exploit that Index() of null attribute is zero. bool wind = (WindRule().Index() != 0); iPainter->drawPolygon(arr, wind); arr.resetRawData(&pv[0], n); } IpeAttribute stroke = Stroke(); if (!stroke.IsNullOrVoid()) { // Now 'stroke' must be an absolute color assert(stroke.IsAbsolute()); IpeColor strokeColor = rep->ToColor(stroke); iPainter->setBrush(QPainter::NoBrush); QPen pen; QColor qstroke(int(strokeColor.iRed * 255), int(strokeColor.iGreen * 255), int(strokeColor.iBlue * 255)); pen.setColor(iDimmed ? qstroke.light() : qstroke); // width if (LineWidth()) { assert(LineWidth().IsAbsolute()); double wid = iZoom * rep->ToScalar(LineWidth()).ToDouble(); pen.setWidth(uint(wid + 0.5)); } // Exploit that Index() of null attribute is zero. switch (LineJoin().Index()) { case IpeStrokeStyle::EMiterJoin: pen.setJoinStyle(Qt::MiterJoin); break; case IpeStrokeStyle::ERoundJoin: pen.setJoinStyle(Qt::RoundJoin); break; case IpeStrokeStyle::EBevelJoin: pen.setJoinStyle(Qt::BevelJoin); break; } switch (LineCap().Index()) { case IpeStrokeStyle::EButtCap: pen.setCapStyle(Qt::FlatCap); break; case IpeStrokeStyle::ERoundCap: pen.setCapStyle(Qt::RoundCap); break; case IpeStrokeStyle::ESquareCap: pen.setCapStyle(Qt::SquareCap); break; } IpeAttribute dash = DashStyle(); if (!dash.IsNullOrSolid()) { // now comes the tricky part if (dash == iDash[0]) pen.setStyle(Qt::DashLine); else if (dash == iDash[1]) pen.setStyle(Qt::DotLine); else if (dash == iDash[2]) pen.setStyle(Qt::DashDotLine); else if (dash == iDash[3]) pen.setStyle(Qt::DashDotDotLine); else pen.setStyle(Qt::DotLine); } iPainter->setPen(pen); QPoint org; std::vector pv; pv.reserve(iV.size()); uint k = 0; for (uint i = 0; i < iType.size(); ++i) { switch (iType[i]) { case EMoveTo: org = iV[k]; // fall through case ELineTo: pv.push_back(iV[k++]); break; case ECurveTo: { QPointArray ctrlPts(4); ctrlPts.setPoint(0, iV[k-1]); ctrlPts.setPoint(1, iV[k++]); ctrlPts.setPoint(2, iV[k++]); ctrlPts.setPoint(3, iV[k++]); #if QT_VERSION >= 0x030000 QPointArray arr = ctrlPts.cubicBezier(); #else QPointArray arr = ctrlPts.quadBezier(); #endif std::copy(arr.begin(), arr.end(), std::back_inserter(pv)); } break; case EEndClosedPath: #ifdef WIN32 pv.push_back(org); #endif case EEndPath: { QPointArray arr; int n = pv.size(); arr.setRawData(&pv[0], n); #ifndef WIN32 // drawPolygon appears to be buggy, at least on Qt 2.3.0 on Win2000 if (iType[i] == EEndClosedPath) iPainter->drawPolygon(arr); else #endif iPainter->drawPolyline(arr); arr.resetRawData(&pv[0], n); pv.clear(); break; } } } } iType.clear(); iV.clear(); } void IpeCanvasPainter::DoDrawBitmap(IpeBitmap bitmap) { if (!bitmap.RenderData()) { // todo: should we remember if this failed? IpeBuffer data = bitmap.PixelData(); if (data.size() > 0) { IpeRenderData *render = new IpeRenderData; // warning: data buffer must remain alive as long as img is QImage img((uchar *) data.data(), bitmap.Width(), bitmap.Height(), 32, 0, 0, QImage::IgnoreEndian); if (img.width() > iMaxBitmapSize || img.height() > iMaxBitmapSize) { int wid = img.width(); int ht = img.height(); double factor; if (wid > ht) factor = wid / double(iMaxBitmapSize); else factor = ht / double(iMaxBitmapSize); wid = int(wid / factor + 0.5); ht = int(ht / factor + 0.5); render->iImage = img.smoothScale(wid, ht); } else render->iImage = img.copy(); // img no longer needed bitmap.SetRenderData(render); } } if (bitmap.RenderData()) { IpeRenderData *render = static_cast(bitmap.RenderData()); IpeMatrix tf = Matrix() * IpeMatrix(1.0 / render->iImage.width(), 0.0, 0.0, -1.0 / render->iImage.height(), 0.0, 1.0); QWMatrix m; m.setMatrix(tf.iA[0], tf.iA[1], tf.iA[2], tf.iA[3], tf.iA[4], tf.iA[5]); iPainter->setWorldMatrix(m, true); iPainter->drawImage(0, 0, render->iImage); iPainter->resetXForm(); } } void IpeCanvasPainter::DoDrawText(const IpeText *text) { if (Stroke().IsNullOrVoid()) return; // 'stroke' must be an absolute color assert(Stroke().IsAbsolute()); // Current origin is lower left corner of text box // Draw bounding box rectangle if (!iPretty && !iDimmed) { QPen pen; pen.setColor(Qt::green); pen.setStyle(Qt::DotLine); iPainter->setPen(pen); iPainter->moveTo(QPt(Matrix() * IpeVector(0,0))); iPainter->lineTo(QPt(Matrix() * IpeVector(0, text->TotalHeight()))); iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(), text->TotalHeight()))); iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(), 0))); iPainter->lineTo(QPt(Matrix() * IpeVector(0,0))); iPainter->setPen(QPainter::NoPen); iPainter->setBrush(Qt::green); QPoint ref = QPt(Matrix() * text->Align()); iPainter->drawRect(ref.x() - 3, ref.y() - 3, 6, 6); } const IpeText::XForm *xf = text->GetXForm(); if (!xf || !iFonts) { QString s = QIpe(text->Text()); uint olen = s.length(); int i = s.find('\n'); if (i >= 0) s.truncate(i); s.truncate(30); if (s.length() < olen) s += "..."; IpeColor stroke = StyleSheet()->Repository()->ToColor(Stroke()); QColor col(int(stroke.iRed * 255), int(stroke.iGreen * 255), int(stroke.iBlue * 255)); iPainter->setPen(iDimmed ? col.light() : col); // QRect r = iPainter->fontMetrics().boundingRect(s); QPoint pt = QPt(Matrix().Translation()); pt.setY(pt.y() - iPainter->fontMetrics().descent()); //if (text->IsMiniPage()) //pt.setY(pt.y() + 15.0); iPainter->drawText(pt, s); } else { Transform(IpeMatrix(xf->iStretch.iX, 0, 0, xf->iStretch.iY, 0, 0)); Execute(xf->iStream); } } // -------------------------------------------------------------------- //! Clear PDF argument stack void IpeCanvasPainter::ClearArgs() { while (!iArgs.empty()) { delete iArgs.back(); iArgs.pop_back(); } } void IpeCanvasPainter::Execute(const IpeBuffer &buffer) { IpeBufferSource source(buffer); IpePdfParser parser(source); iFont = 0; iFillRgb = 0x000000; iStrokeRgb = 0x000000; while (!parser.Eos()) { IpePdfToken tok = parser.Token(); if (tok.iType != IpePdfToken::EOp) { const IpePdfObj *obj = parser.GetObject(); if (!obj) break; // no further parsing attempted iArgs.push_back(obj); } else { // its an operator, execute it IpeString op = tok.iString; parser.GetToken(); if (op == "cm") Opcm(); else if (op == "Tf") OpTf(); else if (op == "Td") OpTd(); else if (op == "TJ") OpTJ(); else if (op == "rg") Oprg(false); else if (op == "RG") Oprg(true); else if (op == "g") Opg(false); else if (op == "G") Opg(true); else if (op == "w") Opw(); else if (op == "m") Opm(); else if (op == "l") Opl(); else if (op == "BT") OpBT(); else if (op != "ET") { IpeString a; for (uint i = 0; i < iArgs.size(); ++i) a += iArgs[i]->Repr() + " "; ipeDebug("Op %s (%s)", op.CString(), a.CString()); } ClearArgs(); } } ClearArgs(); } void IpeCanvasPainter::Opg(bool stroke) { if (iArgs.size() != 1 || !iArgs[0]->Number()) return; int gr = int(iArgs[0]->Number()->Value() * 255); if (stroke) iStrokeRgb = qRgb(gr, gr, gr); else iFillRgb = qRgb(gr, gr, gr); } void IpeCanvasPainter::Oprg(bool stroke) { if (iArgs.size() != 3 || !iArgs[0]->Number() || !iArgs[1]->Number() || !iArgs[2]->Number()) return; int r = int(iArgs[0]->Number()->Value() * 255); int g = int(iArgs[1]->Number()->Value() * 255); int b = int(iArgs[2]->Number()->Value() * 255); QColor col(r, g, b); if (iDimmed) col = col.light(); if (stroke) iStrokeRgb = col.rgb(); else iFillRgb = col.rgb(); } void IpeCanvasPainter::Opcm() { if (iArgs.size() != 6) return; IpeMatrix m; for (int i = 0; i < 6; ++i) { if (!iArgs[i]->Number()) return; m.iA[i] = iArgs[i]->Number()->Value(); } Transform(m); } void IpeCanvasPainter::Opw() { if (iArgs.size() != 1 || !iArgs[0]->Number()) return; iLineWid = iArgs[0]->Number()->Value(); } void IpeCanvasPainter::Opm() { if (iArgs.size() != 2 || !iArgs[0]->Number() || !iArgs[1]->Number()) return; IpeVector t(iArgs[0]->Number()->Value(), iArgs[1]->Number()->Value()); iMoveTo = t; } void IpeCanvasPainter::Opl() { if (iArgs.size() != 2 || !iArgs[0]->Number() || !iArgs[1]->Number()) return; IpeVector t(iArgs[0]->Number()->Value(), iArgs[1]->Number()->Value()); QPen pen; pen.setColor(QRgb(iStrokeRgb)); pen.setWidth(uint(iZoom * iLineWid + 0.5)); iPainter->setPen(pen); iPainter->moveTo(QPt(Matrix() * iMoveTo)); iPainter->lineTo(QPt(Matrix() * t)); } void IpeCanvasPainter::OpBT() { iFont = 0; iTextPos = IpeVector::Zero; } void IpeCanvasPainter::OpTf() { if (iArgs.size() != 2 || !iArgs[0]->Name() || !iArgs[1]->Number()) return; IpeString name = iArgs[0]->Name()->Value(); iFontSize = iArgs[1]->Number()->Value(); if (name[0] != 'F') return; int font = IpeLex(name.substr(1)).GetInt(); // ipeDebug("Setting font %d at %g", font, iFontSize); IpeLinear m = Matrix().Linear() * IpeLinear(iFontSize, 0, 0, iFontSize); iFont = iFonts->GetSize(font, m); } void IpeCanvasPainter::OpTd() { if (iArgs.size() != 2 || !iArgs[0]->Number() || !iArgs[1]->Number()) return; IpeVector t(iArgs[0]->Number()->Value(), iArgs[1]->Number()->Value()); iTextPos = iTextPos + t; } void IpeCanvasPainter::OpTJ() { if (!iFont || iArgs.size() != 1 || !iArgs[0]->Array()) return; IpeVector pos = iTextPos; for (int i = 0; i < iArgs[0]->Array()->Count(); ++i) { const IpePdfObj *obj = iArgs[0]->Array()->Obj(i, 0); if (obj->Number()) { pos.iX -= 0.001 * iFontSize * obj->Number()->Value(); } else if (obj->String()) { IpeString s = obj->String()->Value(); for (int j = 0; j < s.size(); ++j) { uchar ch = s[j]; QPoint pt = QPt(Matrix() * pos); DrawChar(ch, iFillRgb, pt.x(), pt.y()); pos.iX += 0.001 * iFontSize * iFont->Face()->Width(ch); } } } } // -------------------------------------------------------------------- //! Draw a glyph. /*! Glyph is drawn with hotspot at position (x,y) (on the pixmap) */ bool IpeCanvasPainter::DrawChar(int ch, QRgb rgb, int x, int y) { if (!iFont) return false; int w = iPixmap->width(); int h = iPixmap->height(); // generate the glyph pixmap int xOffset, yOffset, gw, gh; uchar *p = iFont->GetGlyph(ch, xOffset, yOffset, gw, gh); if (!p) return false; // compute: (x0,y0) = position in destination pixmap // (x1,y1) = position in glyph image // (w0,h0) = size of image transfer int x0, y0, x1, y1, w0, h0; x0 = x - xOffset; y0 = y - yOffset; x1 = 0; y1 = 0; w0 = gw; h0 = gh; if (x0 < 0) { x1 = -x0; w0 += x0; x0 = 0; } if (x0 + w0 > w) w0 = w - x0; if (w0 < 0) return true; if (y0 < 0) { y1 = -y0; h0 += y0; y0 = 0; } if (y0 + h0 > h) h0 = h - y0; if (h0 < 0) return true; QPixmap dst(gw, gh); bitBlt(&dst, x1, y1, iPixmap, x0, y0, w0, h0); QImage image = dst.convertToImage(); if (iFonts->AntiAlias()) { int r = qRed(rgb); int g = qGreen(rgb); int b = qBlue(rgb); // stuff the glyph pixmap into the image for (int yy = 0; yy < gh; ++yy) { uint *dst = (uint *) image.scanLine(yy); for (int xx = 0; xx < gw; ++xx) { int pix = *p++ & 0xff; int pix1 = 0xff - pix; if (pix > 0) { uint bg = *dst; int newr = (pix * r + pix1 * qRed(bg)) / 255; int newg = (pix * g + pix1 * qGreen(bg)) / 255; int newb = (pix * b + pix1 * qBlue(bg)) / 255; *dst = qRgb(newr, newg, newb); } ++dst; } } } else { // one color for (int yy = 0; yy < gh; ++yy) { uint *dst = (uint *) image.scanLine(yy); for (int xx = 0; xx < gw; xx += 8) { int pix = *p++; for (int xx1 = xx; xx1 < xx + 8 && xx1 < gw; ++xx1) { if (pix & 0x80) *dst = rgb; pix <<= 1; ++dst; } } } } dst.convertFromImage(image); bitBlt(iPixmap, x0, y0, &dst, x1, y1, w0, h0); return true; } // --------------------------------------------------------------------