// SVG Scene/Canvas library for cairo & Pango // Copyright 2005 by Liam Breck // Licensed under GNU LGPL http://gnu.org/copyleft/lesser.html // // Home page: http://networkimprov.net/airwrx/awscene.html /* airWRX offers a sophisticated demo of this library: http://networkimprov.net/airwrx/ See also: http://cairographics.org/ & http://pango.org/ Usage Summary An application using this library creates a master AwScene, which implements the SVG object model with a collection of AwSceneEl objects. The application creates client AwSceneR objects, possibly on different hosts, which render the scene using cairo & Pango. The application creates an AwSvgTerm for each client AwSceneR, and uses it to dispatch AwSvgUpdt objects containing scene revisions to the client. The application handles transport to clients by implementing the AwConn interface. The application may use multiple processes to manipulate the AwScene, which is allocated in shared memory. History 25 Jun 05 incremental rendering, separate AwSceneR 15 Jun 05 draw with cairo; add shapes & paths; z-move 01 Jun 05 prototype release; renders using Win32 GDI Todo text via pango deepen svg support rich text layout incremental render provide z-insert apply transforms to point in findTarget out-of-band regions wrappers for languages find public-domain code for heap mgmt on an app-defined memory block reuse deleted slots, or pack scene long text object support text elements in UTF-8 text selection by whole words text subfields: tflow field (az,xywh) list, findField(xy) text caret blinks */ // ring buffer state mgmt // see awshmem.cpp struct Ring { void init(int iMax) { max = iMax; r = w = 0; } int peek(); int read(); int draft(); int write(); int len() { int aR = r, aW = w; return aW > aR ? aW-aR : max-(aR-aW); } int max; int r,w; }; // shared memory mgmt // see awshmem.cpp class ShMem { public: struct Uid { char s[32]; }; ShMem(size_t iSize); ShMem(const Uid& iUid); ~ShMem(); void* mem() {return mMem;} void getId(Uid* oUid) { memcpy(oUid->s, sUid.s, sizeof(sUid.s)); } private: static Uid sUid; static int sCount; void map(const char* iName, size_t iSize); HANDLE mFile; void* mMem; ShMem(const ShMem&); }; typedef int AwSi; // AwScene slot index typedef unsigned int AwTxid; // update transaction id typedef int AwClid; // svgterm id typedef unsigned int AwOID; // unique object id typedef int AwApid; // applet id typedef int AwScid; // scene id #define PI 3.1415926535 // should we use a cairo point struct? struct Pt { int x, y; void inc(int ix, int iy) { x += ix; y += iy; } }; // the svg elements of the scene struct AwSceneEl { AwSi id; // index in AwScene::slot char svgT; // type of the element enum { eNil, eUnset, eGroup, eBase, eGroupEnd, eLine, eRect, eCircle, eEllipse, ePath, ePmoveto, ePlineto, ePcurveto, ePquadraticCurveto, eParcto, ePclosePath, eText, eTflow, eTsel, eImage, eClip, eTransform, eTransformMatrix, eClient, ePaint }; // for application use to identify higher-level meaning of element int widgetT; union { int appid; AwOID objectid; }; char show; // to be or not to be enum { eHide, eShow, eFlip }; union { char begin; // internal use to calc object size struct { AwApid app; AwSi base; AwSi paint; } group; // group's elements begin at base struct { AwSi group; } base; // first element in group references group struct { int nothing; } groupEnd; // sudden end of group, for use in a clist struct { Pt a,b; int cursor; AwSi paint; } line; struct { int x,y,w,h; int cursor; AwSi paint; } rect; struct { int x,y; int r; int cursor; AwSi paint; } circle; struct { int x,y; int rx, ry; int cursor; AwSi paint; } ellipse; struct { int x,y,w,h; AwSi next; int cursor; AwSi paint; } path; struct { int x,y; } moveto, lineto; struct { Pt a,via,b; } curveto; struct { Pt a,b; } quadraticCurveto; struct { int x,y; int rx,ry; float rotate; char large, sweep; } arcto; struct { int nothing; } closePath; struct { int x,y,w,h; int cursor; AwSi paint; int tbuf; // index in AwScene::text AwSi tsel; char tselG; int wMax; // line width for eText struct{int x,y,w,h;} inset; // rect to flow around for eTflow } text; struct { struct { int pos; Pt pt; int w,h; int moved; } caret; struct { int a,z; Pt pta,ptz; } hl; struct { int h,v; } offset; } tsel; // client-specific text params struct { int x,y,w,h; int dataW, dataH; int cursor; char type; char ibuf; // ibuf gives AwScene::mImageSet item } image; enum { eImgRgb24, eImgArgb32, eImgComprss }; struct { int x,y,w,h; } clip; union { struct { float rotate; struct { float h,v; } scale; struct { int x,y; } translate; }; struct { float a,b,c,d,e,f; } matrix; } transform; struct { int x,y,w,h; AwSi next; } clist; // client-specific element list struct { struct { unsigned char r,g,b,a; } fill, stroke; int wStroke; // stroke width } paint; }; union { char end; // internal use to calc object size int status; // marks element for send/render }; enum { eNew, eRev, eSet, eUtd, eRevUtd }; AwTxid txid; // element may be sent/rendered by a specific update transaction union { AwClid clien; // only valid for elements in a clist AwSi prev; // element under this one in z-order; not valid for elements in a clist }; AwSi next; // element over this one in z-order void init(char iT); // initialize a standalone element to the specified type; see awscene.cpp enum { kClientFlag = 0x40000000 }; void clid(AwClid iC) { clien = iC | kClientFlag; } AwClid clid() { return clien & kClientFlag ? clien ^ kClientFlag : -1; } AwSi* paintable() { // retrieve ptr to paint member, if exists switch (svgT) { case eLine: return &line.paint; case eRect: return &rect.paint; case eCircle: return &circle.paint; case eEllipse: return &ellipse.paint; case ePath: return &path.paint; case eText: case eTflow: return &text.paint; case eGroup: return &group.paint; default: return 0; } } }; // text storage for AwScene; will be UTF-8 struct TextBuf { enum { kMax = 512 }; char t[kMax]; // the data int len; // data length bool set; // marks for send/render int next; // next buf in sequence; index in AwScene::text }; // object sent to svgterm on update struct AwSvgUpdt { int count; // number of items in array char type; // type of array enum { eText, eImage, eScene, eEnd, eConnect, eRequestAck, eImport, eCursor, eSound }; AwScid sceneId; // app-specified value char endset; // final packet in an update group unsigned int packetN; // sequence number of packet union { struct { AwClid clientId; } connect[1]; AwSceneEl scene[128]; struct { TextBuf tbuf; int id; } text[32]; struct { int index; bool head; size_t offset; size_t size; union { char type; char buf[1]; }; } image[64]; struct { int port; size_t acksize; } import[1]; struct { char type; } requestAck[1]; struct { int id; } cursor[1]; struct { int id; int repeat; int delay; } sound[1]; }; bool limit() { return count >= sLimit[type]; } size_t getLen(int iCount = -1); // see awsvgterm.cpp int packetLen(); void copy(AwSvgUpdt* iUp, int iCount) { memcpy(&count, &iUp->count, iUp->getLen(iCount)); } static const unsigned char sLimit[16]; }; // connection interface for AwSvgTerm; application must implement this class AwConn { public: enum { eOk, eConnWait, eTermWait }; virtual int send(AwSvgUpdt* iUpdt, size_t iLen, int iPacketN) = 0; virtual bool termWait() { return false; }; }; class AwScene; // queue of updates for a client AwScene // see awsvgterm.cpp class AwSvgTerm { public: static void Init(); static void Process(); AwSvgTerm(); ~AwSvgTerm(); void reset(AwConn* iConn); void bye() { mOkToSend = false; } AwSvgUpdt* next(char iType); int qpos() { return mQring.draft(); } void copy(AwSvgTerm* iSource, int iQpos, int iN); void submit(int iN, AwScid iSceneId=-1, AwScene* iScn = 0, char iDefer=0); enum { kDefer = 1 }; private: enum { kQLen = 128 }; static AwSvgTerm* sListHead; static AwSvgTerm* sNextQ; static AwSvgUpdt sImageUpdt; static size_t sImgHeadLen; AwSvgUpdt mQueue[kQLen]; AwScene* mScene[kQLen]; Ring mQring; bool mCheckCount; int mImageCount; AwConn* mConn; unsigned int mPacketN; bool mOkToSend; AwSvgTerm *mNext, *mPrev; AwSvgTerm(const AwSvgTerm&); }; // application image repository // the shared-memory method for multi-process scene access requires custom memory mgmt // see awshmem.cpp class AwImageSet { public: enum Type { eNone, eGif, eJpeg, eBmp }; static ShMem* New(size_t iSize); int alloc(size_t iSize); void free(int iN); void update(int iN, void* iBuf, size_t iLen, size_t iFrom = 0); void unset(int iN) { mMap[iN].set = false; } void setType(int iN, Type iType) { mMap[iN].type = iType; } unsigned char* get(int iN, size_t* oSize = 0, size_t* oOffset = 0); unsigned char* data(int iN) { return mBuf + mMap[iN].offset; } bool isset(int iN) { return mMap[iN].set; } void size(int iN, int* oWidth, int* oHeight); ShMem::Uid mUid; private: struct { size_t offset; size_t size; bool set; Type type; } mMap[32]; size_t mSize; int mMapN; size_t mBufPos; unsigned char mBuf[1]; AwImageSet(); AwImageSet(const AwImageSet&); }; // svg scene/canvas API // see awscene.cpp class AwScene { public: enum { eCursorNorm, eCursorIbar, eCursorHand, eCursorDrop }; static ShMem* New(int iElN, int iTextN, ShMem* iImageSet=0); // creates scene in shared memory, for use by multiple processes static AwScene* NewLocal(int iElN, int iTextN); // creates scene in heap AwSi ins(AwSi iEl, int iSvg, int iWidget, int iAppid = 0); // insert element over iEl AwSi insClient(AwSi iClist, AwClid iClient, int iSvg, int iWidget, int iAppid = 0); // insert element for client in clist AwSi insPath(AwSi iPath, int iSvg, AwSceneEl* iData=0); // append an item to the path void insSet(AwSceneEl* iE, int iSvg, int iWidget, int iAppid); // internal use void get(AwSi iEl, AwSceneEl* oEl); // copy el into standalone element void getPaint(AwSi iEl, AwSceneEl* oPaint); // obtain paint element for el int cursor(AwSi iEl); // get cursor field void set(AwSi iEl, AwSceneEl* iData, const char* iT = 0, size_t iLen = 0); // rev el from standalone data void setPaint(AwSi iEl, AwSceneEl* iPaint); // set paint element for el void show(AwSi iEl, char iState); // set/toggle show field void setApp(AwSi iGroup, AwApid iApp); // set app field for group void zForward(AwSi iEl); // swap z-pos of el with next el void zBack(AwSi iEl); // swap z-pos of el with prev el AwTxid newTx() { return ++txN; } // get id for a new update transaction void requestUpdate(AwSi iEl, AwTxid iTx) // flag el for update notification by renderGroup() { slot[iEl].status = AwSceneEl::eRev; slot[iEl].txid = iTx; } void incOffset(AwSi iText, AwClid iClient, int iH, int iV); // increment tsel offset of text el void offsetToCaret(AwSi iText, AwClid iClient, int iViewH); // set tsel offset of text el to show caret in view AwSi select(AwSi iText, AwClid iClient); // obtain tsel element for text el void setSelect(AwSi iText, AwClid iClient, int iA, int iZ, int iPos); // set tsel params by range void setSelect(AwSi iText, AwClid iClient, Pt* iPt1, Pt* iPt2, char iChunk); // set tsel params by rect enum { eHlPt, eHlChar, eHlWord, eHlLine }; void revSelect(AwSi iText, int iPos, int iIncr); // adjust tsel params for inserted/deleted range void insText(AwSi iText, int iPos, const char* iT, int iLen); // insert into text el void delText(AwSi iText, int iPos, int iN); // delete from text el int getText(AwSi iText, char* oBuf, int iLen); // get data from text el bool controlText(AwSi iText, AwClid iClient, int iPos, int iKeycode); // apply control key to text el void insetText(AwSi iText, int iX, int iY, int iW, int iH); // set tflow inset void moveBranch(AwSi iEl, int iXd, int iYd); // adjust position of elements from el void deleteBranch(AwSi iEl); // delete elements over el void getExtent(int* oX, int* oY); // obtain size of canvas from 0,0 struct Find { AwSi group, client; AwApid app; Find() { group=client=-1; app=0; } }; AwSi findTarget(Pt* iPt, AwClid iClient, Find* oFind, AwSi iStart = 0); // identify topmost el at pt AwSi findTop(AwSi iEl); // identify topmost el from iEl within group of iEl AwSi findCliEl(AwSi iClist, AwClid iClient); // identify el for client in clist bool findInGroup(AwSi iEl, AwSi iGroup); // confirm el within group int renderGroup(AwSi iEl, char iType, AwSvgTerm* iCq, AwClid iClient = -1, char iAll = 0, AwSvgUpdt** iUpdt = 0); enum { kAll = 1 }; int findRev(AwTxid iTx, AwSi iTop, AwSi* oList, int iMax); // list rendered elements that had requestUpdate() int slotN; // next element id int slotMax; int textN; // next textbuf id int textMax; ShMem::Uid mUid; // name in shared mem AwTxid txN; // next transaction id AwImageSet* mImageSet; // shared image buffer AwSceneEl slot[1]; // shared scene buffer // TextBuf textb[1] defined by macro private: AwScene(); AwScene(const AwScene&); }; class AwImageBuf; struct AwRenderContext; // scene rendering // see awrender.cpp class AwSceneR { public: AwSceneR(int iElN, int iTextN, bool iOutlineRedraw=false); ~AwSceneR(); void update(AwSvgUpdt* iUp); // revs scene elements from the update struct bool getRedraw(RECT* oArea, AwScid iSceneId); // retrieve redraw region and reset void RenderSvg(HDC iHdc, RECT* iRect, AwSi iEl=0, AwRenderContext* iContext=0); // draws the scene, beginning with iEl private: void calcRedraw(RECT* oR, AwSi iStart, Pt iXn, Pt iXo, bool iAll); // internal use int getText(AwSi iText, char* oBuf, int iLen); // internal use int slotN; // next element id int slotMax; int textN; // next textbuf id int textMax; AwScid mSceneId; AwImageBuf* mImages; int mImagesN; AwSceneEl* slot; TextBuf* textb; AwSceneEl* hold; // previous data for newly updated elements bool mShadeRedraw; // test mode to reveal redrawn area AwSceneR(); AwSceneR(const AwSceneR&); }; struct _cairo_surface; struct _cairo; // rendering for app-side measurement of elements // see awrender.cpp class AwRenderUtil { public: static AwRenderUtil* sRt; static void Init(HDC iHdc, HFONT iFont); bool pathHasPoint(AwSceneEl* iScn, AwSi iPath, Pt* iPt); void measureLine(char* iBuf, int iLen, int iWmax, SIZE* oSz); void measureBox(char* iBuf, int iLen, int iW, int* oH); void selectByRange(char* iBuf, int iLen, int iW, int iInsetY, int iInsetH, int iA, int iZ, Pt* oPta, Pt* oPtz, bool iZbottom = false); void selectByPoints(char* iBuf, int iLen, int iW, int iInsetY, int iInsetH, Pt* ioPta, Pt* ioPtz, int* oA, int* oZ); private: HDC mMemDc; _cairo_surface* mCairoSurface; _cairo* mCr; AwRenderUtil() {} AwRenderUtil(const AwRenderUtil&); };