rappresenta il controllo RichEdit che trasforma il testo in immagine.
Per determinare ora la dimensione minima del controllo si utilizza un algoritmo di "binary search". Per prima cosa
si ridimensione il controllo in modo tale che abbia una larghezza eccesiva - ad esempio la larghezza dello schermo - , poi
si invia una richiesta di ridimensionamento (con l'evento EN_REQUESTRESIZE) ed infine si salva l'altezza del controllo ridimensionato.
Essa rappresenta la altezza minima che il controllo necessita per visualizzare il suo contenuto. In seguito si ricerca la larghezza
minima che mantenga l'altezza appena calcolata; per fare ciò utilizzo il ciclo di comandi seguenti:
int cxFirst = 0;
int cxLast = ::GetSystemMetrics( SM_CXFULLSCREEN );
int cyMin = 0;
do
{
//taking a guess
int cx = ( cxFirst + cxLast ) / 2;
// testing it
RECT rc;
rc.left = xEditPos;
rc.top = yEditPos;
rc.right = xEditPos + cx;
rc.bottom = yEditPos + 1 ;
MoveWindow(hRichEditToBmp,
rc.left,
rc.top,
rc.right - rc.left,
rc.bottom - rc.top,
TRUE);
SetWindowPos(hRichEditToBmp,
NULL,
rc.left,
rc.top,
rc.right - rc.left,
rc.bottom - rc.top,
NULL);
SendMessage(hRichEditToBmp, EM_REQUESTRESIZE, 0,0);
// if it's the first guess, take it anyway
if( cyMin == 0 )
cyMin = m_dimMsg.dyI();
// refining the guess
if( m_dimMsg.dyI() > cyMin )
{
cxFirst = cx + 1;
}
else
{
cxLast = cx - 1;
}
}
while( cxFirst < cxLast );
dove m_dimMsg mantiene le dimensioni del controllo.
Ora per ottenere la Bitmap del contenuto del RichEdit si crea un apposito contesto grafico
- con una bitmap selezionata al suo interno - e lo si utilizza mandando un comando "EM_FORMATRANGE" al
controllo. Il RichEdit a questo punto riproduce il testo sulla Bitmap.
Il codice utilizzato è il seguente:
RECT rEdit;
GetClientRect(hRichEditToBmp, &rEdit);
hbmp = CreateCompatibleBitmap(bmpDC, rEdit.right - rEdit.left,
rEdit.bottom - rEdit.top);
SelectObject(bmpDC, hbmp);
rEdit.right = rEdit.right*72*20/
(GetDeviceCaps(bmpDC, LOGPIXELSY));
rEdit.bottom = rEdit.bottom*72*20/
(GetDeviceCaps(bmpDC,LOGPIXELSY));
FORMATRANGE fr;
fr.hdc = bmpDC;
fr.hdcTarget = bmpDC;
fr.rc = rEdit;
fr.rcPage = rEdit;
fr.chrg.cpMin = 0;
fr.chrg.cpMax = -1;
SendMessage(hRichEditToBmp, EM_FORMATRANGE, TRUE, (LPARAM)(&fr));
dove nella struttura FORMATRANGE fr si deve indicare il rettangolo
in twips su cui il controllo disegna. Si osservi che bmpDC è il
contesto grafico in cui è selezionata la Bitmap bmp su cui viene
renderizzato il testo.
Un modo semplice per supportare lo zoom - punto [b] - a questo punto è quello di compiere una trasformazione
di coordinate sul contesto grafico bmpDC, prima che venga mandato il
comando EM_FORMATRANGE. Il codice al riguardo è il seguente:
XFORM stX;
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = 1.0 * (Zoom);
stX.eM22 = 1.0 * (Zoom);
SetGraphicsMode ( bmpDC, GM_ADVANCED);
BOOL ret = SetWorldTransform(bmpDC,&stX);
dove si osservi che è necessaria la modalità GM_ADVANCED - presente a partire
da Windows 2000 - e che le componenti modificate della matrice che compie la trasformazione sono
solo quelle sulla diagonale (eM11 e eM22).
Questo modo di operare lo zoom risulta essere un poco brutale e, fatte alcune prove, ha il grave problema di
sgranare la bitmap rendendo il testo meno leggibile.
Il codice brevemente descritto si trova nella classe CPdfNotes dei sorgenti di ePDF. Per comprendere meglio il
processo seguito può essere utile una
lettura della documentazione della MSDN relativa al RichEdit Control e anche l'articolo di CodeProject da cui
si è preso spunto per determinare la dimensione minima del testo - raggiungibile all'indirizzo
[Calculating a Rich Edit Control Minimum Size].
Secondo metodo
La procedura descritta nel punto precedente presenta due difetti:
a. Per renderizzare un testo è necessaria una dialog che a volte si deve nascondere - vedi ad esempio quando
è necessario determinare la bitmap per un testo RTF salvato. In questo caso inoltre si osserva durente il
processo di rendering il flickering dell'applicazione a causa della attività di "paint".
b. Come già indicato in precedenza la procedura di zoom sgrana l'immagine del testo e lo rende meno leggibile.
Per superare questa duplice difficoltà ho deciso di utilizzare il Windowless Richedit Control che Microsoft fornisce
a partire da Windows 2000. La documentazione al riguardo è molto scarsa ma un articolo interessante si trova all'indirizzo
[ITextSevices -- Using the Windowless RichEdit for Drawing RTF].
L'implementazione segue quanto indicato nell'articolo salvo alcune modifiche che vado ora a indicare.
Il tutto è stato utilizzato nella demo e nei sorgenti che ho allegato alla presente nota.
La versione 2.0 del RichEdit Control è fornita con un'insieme di interfacce COM che consentono di operare su di esso: quelle
utilizzate dal mio applicativo sono solamente ITextHost e ITextSevices. ITextDocument non è utilizzata
in quanto non vi è alcuna operazione di formattazione sul testo. La classe che implementa il Windowless RichEdit Control si chiama
CRtf2Bmp: essa implementa ITextHost e IRtf2BMP. La prima fornisce tutti i metodi per interagire con il RichEdit
sosttostante, mentre la seconda presenta i metodi che l'applicazione di prova può richiamare per interagire con la classe. L'Interfaccia
IRtf2BMP è riportata qui di seguito:
interface IRtf2Bmp
{
public:
virtual ~IRtf2Bmp() {};
virtual HRESULT get_NaturalHeight(long Width, long *pVal) = 0;
virtual HRESULT get_NaturalWidth(long Height, long *pVal) = 0;
virtual HRESULT get_NaturalSize(long *Height, long *Width) = 0;
virtual HRESULT Create() = 0;
virtual HRESULT Draw(void *hdcDraw, RECT *prc) = 0;
virtual HRESULT get_RTFText(CString *str) = 0;
virtual HRESULT put_RTFText(CString str) = 0;
virtual void setZoom(double zoom)=0;
virtual void setParent(HWND hParent)=0;
// COM-like functions
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
};
le cui funzioni principali sono:
-> get_NaturalSize(long *Height, long *Width): determina la larghezza e
l'altezza del testo da trasformare in Bitmap. E' l'analogo dell'algoritmo di 'binary search' descritto
in precedenza;
-> put_RTFText(CString str): inserisce il testo nel Rich Edit control;
-> Draw(void *hdcDraw, RECT *prc): disegna il testo nel contesto grafico e nel rettangolo indicato;
-> setZoom(double zoom): Setta lo zoom a cui effettuare il rendering.
La demo consente di inserire un numero illimitato di note con testo fisso ("Pippo e Pluto Topolino") sulla schermata
principale dell'applicativo. L'inserimento avviene con il doppio click del mouse. E' possibile scegliere lo zoom a cui
inserire la nota ed inoltre renderizzare tutte le note allo zoom prefissato. Le note possono essere spostate una volta
selezionate ma non possono essere cancellate.
Come già anticipato in precedenza la dimensione del testo da trasformare in Bitmap si determina con la funzione get_NaturalSize
che riporto qui di seguito:
HRESULT CRtf2Bmp::get_NaturalSize(long *Height, long *Width)
{
long lWidth = 0;
long lHeight = 0;
SIZEL szExtent;
HDC hdcDraw;
if (!m_spTextServices)
return S_FALSE;
hdcDraw = GetDC(NULL);
szExtent.cy = 1;
szExtent.cx = 10000;
lWidth = 10000;
m_spTextServices->TxGetNaturalSize(DVASPECT_CONTENT,
hdcDraw,
NULL,
NULL,
TXTNS_FITTOCONTENT,
&szExtent,
&lWidth,
&lHeight);
ReleaseDC(NULL, hdcDraw);
*Width = lWidth;
*Height = lHeight;
return S_OK;
}
Come nel caso del 'Binary Search' la dimensione che si impone al RichEdit control è quella di una
larghezza molto ampia e di una altezza molto piccola (pari a 1). Tramite
l'interfaccia ITextServices m_spTextServices si richiede poi al controllo di restituire le dimensioni
minime del testo, salvate nelle variabili lWidth e lHeight.
La renderizzazione del testo avviene invece tramite la funzione Draw(void *hdcDraw, RECT *prc) a cui
si fornisce l'appropriato contesto grafico per ottenere la Bitmap ed il rettangolo su cui disegnare. La funzione ha la struttura
seguente:
HRESULT CRtf2Bmp::Draw(void *hdcDraw, RECT *prc)
{
HRESULT hr;
LRESULT lResult;
hr = m_spTextServices->TxDraw(
DVASPECT_CONTENT,
0,
NULL,
NULL,
(HDC) hdcDraw,
NULL,
(RECTL *) prc,
NULL,
(RECT *) NULL,
NULL,
NULL,
TXTVIEW_INACTIVE);
return hr;
}
Come nel caso della determinazione delle dimensioni del testo anche in questo caso si utilizza un metodo
dell'interfaccia ITextServices a cui semplicemente si fornisce il contesto grafico ed il rettangolo in cui disegnare.
La funzione infine che crea la bitmap del testo è semplicemente una sequenza di comandi che viene fatta quando si
clicca due volte sulla form dell'applicativo ed è riportata qui di seguito:
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
CString str;
long h, w;
str = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1040{\\fonttbl" +
"{\\f0\\fswiss\\fcharset0 Arial;}}" +
+ "{\\*\\generator Msftedit 5.41.15.1507;}" +
"\\viewkind4\\uc1\\pard\\f0\\fs20 pippo\\" +
"par e\\par pluto topolino\\par}";
m_Rtf2Bmp->put_RTFText(str);
//Setto lo zoom
m_Rtf2Bmp->setZoom(NULL);
m_Rtf2Bmp->get_NaturalSize(&h, &w);
RectD r;
r.x = x;
r.y = y;
r.dx = w;
r.dy = h;
//Creo la bitmap...
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC bmpDC = CreateCompatibleDC(hdc);
HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h);
SelectObject(bmpDC, bmp);
BRUSH hbrBkgnd = CreateSolidBrush(RGB(255,255,255));
RECT r1;
r1.left = 0;
r1.top = 0;
r1.right = w;
r1.bottom = h;
FillRect(bmpDC, &r1, hbrBkgnd);
DeleteObject(hbrBkgnd);
m_Rtf2Bmp->Draw(bmpDC, &r1);
EndPaint(hwnd, &ps);
//Aggiungo la nota
AddNote(r, str,bmp, 0);
dove str è la stringa che viene visualizzata, AddNote(..)
la funzione che aggiunge una nota alla lista da disegnare.
Come si osserva dal codice la sequenza di comandi per ottenere la Bitmap del testo è:
a. Settare la stringa.
b. Settare lo zoom - il parametro NULL indica di usare lo zoom corrente.
c. Determinare le dimensioni del testo
d. Creare un contesto grafico compatibile in cui selezionare una Bitmap delle dimensioni corette su cui verrà
effettuato il disegno.
e. Disegnare il rettangolo di background. Se non lo si fa il colore di sfondo rimane nero.
f. Renderizzare il testo con la funzione Draw di CRtf2Bmp
g. Aggiungere una nota al disegno
Questo secondo metodo verà implementato in una versione successiva di ePDF per supportare lo zoom delle note.