/* Miranda IM Help Plugin Copyright (C) 2002 Richard Hughes, 2005-2007 H. Herkenrath This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (Help-License.txt); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #define __RPCASYNC_H__ /* header shows warnings in VS6 */ #include #if !defined(VK_OEM_PLUS) #define _WIN32_WINNT 0x0500 #include #endif #define MIRANDA_VER 0x0600 #include #include #include "help.h" #include struct EditStreamData { PBYTE pbBuff; int cbBuff; int iCurrent; }; #ifndef EDITOR struct HyperlinkData { CHARRANGE range; char *szLink; } static *hyperlink=NULL; static int hyperlinkCount=0; #endif static DWORD CALLBACK EditStreamInRtf(DWORD dwCookie,LPBYTE pbBuff,LONG cb,LONG *pcb) { struct EditStreamData *esd=(struct EditStreamData*)dwCookie; *pcb=min(esd->cbBuff-esd->iCurrent,cb); CopyMemory(pbBuff,esd->pbBuff,*pcb); esd->iCurrent+=*pcb; return 0; } #ifdef EDITOR static DWORD CALLBACK EditStreamOutRtf(DWORD dwCookie,LPBYTE pbBuff,LONG cb,LONG *pcb) { struct EditStreamData *esd=(struct EditStreamData*)dwCookie; PBYTE buf=(PBYTE)mir_realloc(esd->pbBuff,esd->cbBuff+cb+1); if(buf==NULL) return 0; esd->pbBuff=buf; esd->cbBuff+=cb; CopyMemory(esd->pbBuff+esd->iCurrent,pbBuff,cb); esd->iCurrent+=cb; esd->pbBuff[esd->iCurrent]='\0'; *pcb=cb; return 0; } #endif struct { const char *szSym; char ch; } static const htmlSymbolChars[]={ {"lt",'<'}, {"gt",'>'}, {"amp",'&'}, {"quot",'\"'}, {"nbsp",' '}, }; struct { const char *szName; const char *szClr; } static const htmlColourNames[]={ {"black","000000"}, {"maroon","800000"}, {"green","008000"}, {"olive","808000"}, {"navy","000080"}, {"purple","800080"}, {"teal","008080"}, {"silver","C0C0C0"}, {"gray","808080"}, {"red","FF0000"}, {"lime","00FF00"}, {"yellow","FFFF00"}, {"blue","0000FF"}, {"fuchsia","FF00FF"}, {"aqua","00FFFF"}, {"white","FFFFFF"}, }; // a quick test to see who's read their comp.lang.c FAQ: #define stringize2(n) #n #define stringize(n) stringize2(n) struct { const char *szHtml; const char *szRtf; } static const simpleHtmlRtfConversions[]={ {"i","i"}, {"/i","i0"}, {"b","b"}, {"/b","b0"}, {"u","ul"}, {"/u","ul0"}, {"big","fs" stringize(TEXTSIZE_BIG)}, {"/big","fs" stringize(TEXTSIZE_NORMAL)}, {"small","fs" stringize(TEXTSIZE_SMALL)}, {"/small","fs" stringize(TEXTSIZE_NORMAL)}, {"/font","cf0"} }; // mir_free() the return value char *GetHtmlTagAttribute(const char *pszTag,const char *pszAttr) { int iAttrName,iAttrNameEnd,iAttrEquals,iAttrValue,iAttrValueEnd,iAttrEnd; int attrLen=lstrlenA(pszAttr); for(iAttrName=0;!isspace(pszTag[iAttrName]) && pszTag[iAttrName]!='>';iAttrName++); for(;;) { for(;isspace(pszTag[iAttrName]);iAttrName++); if(pszTag[iAttrName]=='>' || pszTag[iAttrName]=='\0') break; for(iAttrNameEnd=iAttrName;isalnum(pszTag[iAttrNameEnd]);iAttrNameEnd++); for(iAttrEquals=iAttrNameEnd;isspace(pszTag[iAttrEquals]);iAttrEquals++); if(pszTag[iAttrEquals]!='=') {iAttrName=iAttrEquals; continue;} for(iAttrValue=iAttrEquals+1;isspace(pszTag[iAttrValue]);iAttrValue++); if(pszTag[iAttrValue]=='>' || pszTag[iAttrValue]=='\0') break; if(pszTag[iAttrValue]=='"' || pszTag[iAttrValue]=='\'') { for(iAttrValueEnd=iAttrValue+1;pszTag[iAttrValueEnd] && pszTag[iAttrValueEnd]!=pszTag[iAttrValue];iAttrValueEnd++); iAttrValue++; iAttrEnd=iAttrValueEnd+1; } else { for(iAttrValueEnd=iAttrValue;pszTag[iAttrValueEnd] && pszTag[iAttrValueEnd]!='>' && !isspace(pszTag[iAttrValueEnd]);iAttrValueEnd++); iAttrEnd=iAttrValueEnd; } if(pszTag[iAttrValueEnd]=='\0') break; if(attrLen==iAttrNameEnd-iAttrName && !_strnicmp(pszAttr,pszTag+iAttrName,attrLen)) { char *szValue; szValue=(char*)mir_alloc(iAttrValueEnd-iAttrValue+1); if(szValue!=NULL) { CopyMemory(szValue,pszTag+iAttrValue,iAttrValueEnd-iAttrValue); szValue[iAttrValueEnd-iAttrValue]='\0'; } return szValue; } iAttrName=iAttrEnd; } return NULL; } void StreamInHtml(HWND hwndEdit,const char *szHtml,UINT codepage,COLORREF clrBkgrnd) { EDITSTREAM stream; struct EditStreamData esd; struct ResizableCharBuffer header,body; COLORREF *colourTbl=NULL; int colourTblCount=0; const char *pszHtml; char *szThisTagHref=NULL; int keywordAtBeginning=1,paragraphBefore=0,lineBreakBefore=1; int charCount=0; ZeroMemory(&stream,sizeof(stream)); ZeroMemory(&esd,sizeof(esd)); ZeroMemory(&header,sizeof(header)); ZeroMemory(&body,sizeof(body)); #ifndef EDITOR FreeHyperlinkData(); #endif AppendToCharBuffer(&header,"{\\rtf1\\ansi\\ansicpg%u\\deff0{\\fonttbl{\\f0 Tahoma;}}",codepage); for(pszHtml=szHtml;*pszHtml!='\0';) { if(*pszHtml=='<') { const char *pszTagEnd; int iNameEnd,i; char szTagName[16]; pszTagEnd=strchr(pszHtml+1,'>'); if(pszTagEnd==NULL) break; for(iNameEnd=1;pszHtml[iNameEnd]!='\0' && pszHtml[iNameEnd]!='>' && !isspace(pszHtml[iNameEnd]);iNameEnd++); CopyMemory(szTagName,pszHtml+1,min(sizeof(szTagName),iNameEnd)); szTagName[min(sizeof(szTagName),iNameEnd)-1]='\0'; for(i=0;i hyperlink=buf; hyperlink[hyperlinkCount].range.cpMin=paragraphBefore?(charCount+2):charCount; hyperlink[hyperlinkCount].range.cpMax=-1; hyperlink[hyperlinkCount].szLink=NULL; } else { mir_free(szThisTagHref); szThisTagHref=NULL; } } #endif } else if(!lstrcmpiA(szTagName,"/a")) { if(szThisTagHref) { #ifdef EDITOR AppendToCharBuffer(&body,":%s\\strike0 ",szThisTagHref); mir_free(szThisTagHref); #else #if defined(_UNICODE) mir_utf8decodecp(szThisTagHref,CP_ACP,NULL); #endif hyperlink[hyperlinkCount].range.cpMax=charCount; hyperlink[hyperlinkCount].szLink=szThisTagHref; hyperlinkCount++; #endif szThisTagHref=NULL; } } else if(!lstrcmpiA(szTagName,"font")) { char *szColour=GetHtmlTagAttribute(pszHtml,"color"); if(szColour!=NULL) { int i,freeColour=1; if(szColour[0]!='#' || lstrlenA(szColour)!=7) { for(i=0;i=0x100) AppendToCharBuffer(&body,"\\u%d",ch); else AppendToCharBuffer(&body,"\\'%02x ",ch); } else { for(i=0;i=' ') charCount++; if(*pszHtml=='\\' || *pszHtml=='{' || *pszHtml=='}') AppendCharToCharBuffer(&body,'\\'); AppendCharToCharBuffer(&body,*pszHtml++); } else pszHtml++; } mir_free(szThisTagHref); // does NULL check { int i; COLORREF clr=GetSysColorBrush(COLOR_HOTLIGHT)?GetSysColor(COLOR_HOTLIGHT):RGB(0,0,255); AppendToCharBuffer(&header,"{\\colortbl ;\\red%d\\green%d\\blue%d;",GetRValue(clr),GetGValue(clr),GetBValue(clr)); for(i=0;i=hyperlink[i].range.cpMin && cpPos<=hyperlink[i].range.cpMax) { if(pcpMin) *pcpMin=hyperlink[i].range.cpMin; if(pcpMax) *pcpMax=hyperlink[i].range.cpMax; if(ppszLink) *ppszLink=hyperlink[i].szLink; return 1; } if(pcpMin) *pcpMin=-1; if(pcpMax) *pcpMax=-1; if(ppszLink) *ppszLink=NULL; return 0; } #endif // !defined EDITOR #ifdef EDITOR struct RtfGroupStackData { BYTE bold,italic,underline,strikeout; BYTE isDestination,isColourTbl,isFontTbl; int colour; int fontSize; int unicodeSkip; int charset; }; char *StreamOutHtml(HWND hwndEdit) { EDITSTREAM stream; struct EditStreamData esd; struct ResizableCharBuffer htmlOut,hyperlink,*output; COLORREF *colourTbl=NULL; int colourTblCount=0; struct RtfGroupStackData *groupStack; int groupLevel; int inFontTag=0,inAnchorTag=0,inBigTag=0,inSmallTag=0,lineBreakBefore=0; char *pszRtf; int *fontTblCharsets=NULL; int fontTblCount=0; int normalTextSize=0; void *buf; ZeroMemory(&stream,sizeof(stream)); ZeroMemory(&esd,sizeof(esd)); ZeroMemory(&htmlOut,sizeof(htmlOut)); ZeroMemory(&hyperlink,sizeof(hyperlink)); ZeroMemory(&output,sizeof(output)); stream.dwCookie=(DWORD)&esd; stream.pfnCallback=EditStreamOutRtf; #if defined(_UNICODE) SendMessage(hwndEdit,EM_STREAMOUT,(WPARAM)(CP_UTF8<<16)|SF_USECODEPAGE|SF_RTFNOOBJS|SFF_PLAINRTF,(LPARAM)&stream); #else SendMessage(hwndEdit,EM_STREAMOUT,SF_RTFNOOBJS|SFF_PLAINRTF,(LPARAM)&stream); #endif if(esd.pbBuff==NULL) return NULL; output=&htmlOut; groupStack=(struct RtfGroupStackData*)mir_calloc(sizeof(struct RtfGroupStackData)); if(groupStack!=NULL) { groupLevel=0; groupStack[0].unicodeSkip=1; for(pszRtf=(char*)esd.pbBuff;*pszRtf!='\0';) { if(*pszRtf=='{') { buf=(struct RtfGroupStackData*)mir_realloc(groupStack,sizeof(struct RtfGroupStackData)*(groupLevel+2)); if(buf==NULL) break; groupStack=(struct RtfGroupStackData*)buf; groupStack[groupLevel]=groupStack[groupLevel]; groupLevel++; pszRtf++; } else if(*pszRtf=='}') { groupLevel--; if(groupStack[groupLevel].bold!=groupStack[groupLevel+1].bold) AppendToCharBuffer(output,groupStack[groupLevel].bold?"":""); if(groupStack[groupLevel].italic!=groupStack[groupLevel+1].italic) AppendToCharBuffer(output,groupStack[groupLevel].bold?"":""); if(groupStack[groupLevel].underline!=groupStack[groupLevel+1].underline) AppendToCharBuffer(output,groupStack[groupLevel].bold?"":""); if(groupStack[groupLevel].strikeout!=groupStack[groupLevel+1].strikeout && groupStack[groupLevel+1].strikeout) if(inAnchorTag) {AppendToCharBuffer(output,""); inAnchorTag=0;} if(groupStack[groupLevel].colour!=groupStack[groupLevel+1].colour) if(inFontTag) {AppendToCharBuffer(output,""); inFontTag=0;} if(groupStack[groupLevel].fontSize!=groupStack[groupLevel+1].fontSize) { if(inBigTag) {AppendToCharBuffer(output,""); inBigTag=0;} if(inSmallTag) {AppendToCharBuffer(output,""); inSmallTag=0;} if(groupStack[groupLevel].fontSize"); inSmallTag=1;} else if(groupStack[groupLevel].fontSize>normalTextSize) {AppendToCharBuffer(output,""); inBigTag=1;} } if(groupLevel==0) break; pszRtf++; } else if(*pszRtf=='\\' && pszRtf[1]=='*') { groupStack[groupLevel].isDestination=1; pszRtf+=2; } else if(*pszRtf=='\\' && pszRtf[1]=='\'') { char szHex[3]="\0\0"; char szChar[2]; szHex[0]=pszRtf[2]; if(pszRtf[2]) szHex[1]=pszRtf[3]; else pszRtf--; szChar[0]=(char)strtol(szHex,NULL,16); szChar[1]='\0'; if(groupStack[groupLevel].charset) { WCHAR szwChar[2]; CHARSETINFO csi; TranslateCharsetInfo((PDWORD)groupStack[groupLevel].charset,&csi,TCI_SRCCHARSET); MultiByteToWideChar(csi.ciACP,0,szChar,1,szwChar,2); AppendToCharBuffer(output,"&#%u;",(WORD)szwChar[0]); } else AppendToCharBuffer(output,"&#%u;",(BYTE)szChar[0]); pszRtf+=4; } else if(*pszRtf=='\\' && isalpha(pszRtf[1])) { char szControlWord[32]; int iWordEnd; int hasParam=0; int param=-1; for(iWordEnd=1;isalpha(pszRtf[iWordEnd]);iWordEnd++); CopyMemory(szControlWord,pszRtf+1,min(sizeof(szControlWord),iWordEnd)); szControlWord[min(sizeof(szControlWord),iWordEnd)-1]='\0'; if(isdigit(pszRtf[iWordEnd]) || pszRtf[iWordEnd]=='-') { hasParam=1; param=strtol(pszRtf+iWordEnd,&pszRtf,10); } else pszRtf=pszRtf+iWordEnd; if(*pszRtf==' ') pszRtf++; if(!lstrcmpiA(szControlWord,"colortbl")) { groupStack[groupLevel].isColourTbl=1; buf=(COLORREF*)mir_realloc(colourTbl,sizeof(COLORREF)); if(buf!=NULL) { colourTbl=(COLORREF*)buf; colourTblCount=1; colourTbl[0]=0; } groupStack[groupLevel].isDestination=1; } else if(!lstrcmpiA(szControlWord,"fonttbl")) { groupStack[groupLevel].isFontTbl=1; groupStack[groupLevel].isDestination=1; } else if(!lstrcmpiA(szControlWord,"stylesheet")) { groupStack[groupLevel].isDestination=1; } else if(!lstrcmpiA(szControlWord,"red")) { if(!hasParam || !colourTblCount) break; colourTbl[colourTblCount-1]&=~RGB(255,0,0); colourTbl[colourTblCount-1]|=RGB(param,0,0); } else if(!lstrcmpiA(szControlWord,"green")) { if(!hasParam || !colourTblCount) break; colourTbl[colourTblCount-1]&=~RGB(0,255,0); colourTbl[colourTblCount-1]|=RGB(0,param,0); } else if(!lstrcmpiA(szControlWord,"blue")) { if(!hasParam || !colourTblCount) break; colourTbl[colourTblCount-1]&=~RGB(0,0,255); colourTbl[colourTblCount-1]|=RGB(0,0,param); } else if(!lstrcmpiA(szControlWord,"f")) { if(groupStack[groupLevel].isFontTbl) { buf=(int*)mir_realloc(fontTblCharsets,sizeof(int)*(fontTblCount+1)); if(buf!=NULL) { fontTblCharsets=(int*)buf; fontTblCharsets[fontTblCount]=0; fontTblCount++; } } else { if(hasParam && param>=0 && param"); if(hasParam && param) { int i; char szColour[7]; wsprintfA(szColour,"%02x%02x%02x",GetRValue(colourTbl[param]),GetGValue(colourTbl[param]),GetBValue(colourTbl[param])); for(i=0;i",htmlColourNames[i].szName); break; } } if(i==SIZEOF(htmlColourNames)) AppendToCharBuffer(output,"",szColour); inFontTag=1; groupStack[groupLevel].colour=param; } else groupStack[groupLevel].colour=0; } else if(!lstrcmpiA(szControlWord,"fs")) { if(normalTextSize==0 && hasParam) { normalTextSize=param; groupStack[0].fontSize=normalTextSize; } if(inBigTag) {AppendToCharBuffer(output,""); inBigTag=0;} if(inSmallTag) {AppendToCharBuffer(output,""); inSmallTag=0;} if(hasParam) { groupStack[groupLevel].fontSize=param; if(groupStack[groupLevel].fontSize"); inSmallTag=1;} else if(groupStack[groupLevel].fontSize>normalTextSize) {AppendToCharBuffer(output,""); inBigTag=1;} } } else if(!lstrcmpiA(szControlWord,"uc")) { if(hasParam) groupStack[groupLevel].unicodeSkip=param; } else if(!lstrcmpiA(szControlWord,"u")) { if(hasParam) { AppendToCharBuffer(output,"&#%u;",param); pszRtf+=groupStack[groupLevel].unicodeSkip; } } else if(!lstrcmpiA(szControlWord,"b")) { if(!hasParam || param) { groupStack[groupLevel].bold=1; AppendToCharBuffer(output,""); } else { groupStack[groupLevel].bold=0; AppendToCharBuffer(output,""); } } else if(!lstrcmpiA(szControlWord,"i")) { if(!hasParam || param) { groupStack[groupLevel].italic=1; AppendToCharBuffer(output,""); } else { groupStack[groupLevel].italic=0; AppendToCharBuffer(output,""); } } else if(!lstrcmpiA(szControlWord,"ul")) { if(!hasParam || param) { groupStack[groupLevel].underline=1; AppendToCharBuffer(output,""); } else { groupStack[groupLevel].underline=0; AppendToCharBuffer(output,""); } } else if(!lstrcmpiA(szControlWord,"ulnone")) { groupStack[groupLevel].underline=0; AppendToCharBuffer(output,""); } else if(!lstrcmpiA(szControlWord,"strike")) { if(!hasParam || param) { groupStack[groupLevel].strikeout=1; mir_free(hyperlink.sz); // does NULL check hyperlink.iEnd=hyperlink.cbAlloced=0; hyperlink.sz=NULL; output=&hyperlink; } else { groupStack[groupLevel].strikeout=0; if(hyperlink.iEnd && hyperlink.sz!=NULL) { char *pszColon; output=&htmlOut; pszColon=strchr(hyperlink.sz,':'); if(pszColon==NULL) pszColon=""; else *pszColon++='\0'; AppendToCharBuffer(output,"%s",pszColon,hyperlink.sz); mir_free(hyperlink.sz); hyperlink.iEnd=hyperlink.cbAlloced=0; hyperlink.sz=NULL; } } } else if(!lstrcmpiA(szControlWord,"par")) { if(lineBreakBefore) AppendToCharBuffer(output,"
"); lineBreakBefore=1; // richedit puts a \par right at the end } } else { int i; if(*pszRtf=='\\') pszRtf++; if(!groupStack[groupLevel].isDestination) { if(lineBreakBefore && (BYTE)*pszRtf>=' ') {AppendToCharBuffer(output,"
"); lineBreakBefore=0;} if(*pszRtf==' ') AppendCharToCharBuffer(output,*pszRtf); else { for(i=0;i