// // TEXTOBJ.CPP // Drawing objects: point, openpolyline, closepolyline, ellipse // // Copyright Microsoft 1998- // #include "precomp.h" #include "nmwbobj.h" TextObj::TextObj(void) { #ifdef _DEBUG FillMemory(&m_textMetrics, sizeof(m_textMetrics), DBG_UNINIT); #endif // _DEBUG // // ALWAYS ZERO OUT m_textMetrics. Calculations depend on the height // and width of chars being zero before the font is set. // ZeroMemory(&m_textMetrics, sizeof(m_textMetrics)); SetMyWorkspace(NULL); SetOwnerID(g_MyMemberID); m_ToolType = TOOLTYPE_TEXT; // // Created locally, not selected, not editing or deleting. // CreatedLocally(); ClearSelectionFlags(); ClearEditionFlags(); ClearDeletionFlags(); SetType(siNonStandardPDU_chosen); SetFillColor(RGB(-1,-1,-1),TRUE); SetZOrder(front); // // No attributes changed, they will be set as we change them // SetWorkspaceHandle(g_pCurrentWorkspace == NULL ? 0 : g_pCurrentWorkspace->GetWorkspaceHandle()); SetType(drawingCreatePDU_chosen); SetROP(R2_NOTXORPEN); SetPlaneID(1); SetMyPosition(NULL); SetMyWorkspace(NULL); // 1 Pixels for pen thickness SetPenThickness(2); SetAnchorPoint(0,0); RECT rect; ::SetRectEmpty(&rect); SetRect(&rect); SetBoundsRect(&rect); m_hFontThumb = ::CreateFont(0,0,0,0,FW_NORMAL,0,0,0,0,OUT_TT_PRECIS, CLIP_DFA_OVERRIDE, DRAFT_QUALITY,FF_SWISS,NULL); m_hFont = ::CreateFont(0,0,0,0,FW_NORMAL,0,0,0,0,OUT_TT_PRECIS, CLIP_DFA_OVERRIDE, DRAFT_QUALITY, FF_SWISS,NULL); m_nKerningOffset = 0; ResetAttrib(); } TextObj::~TextObj( void ) { RemoveObjectFromResendList(this); RemoveObjectFromRequestHandleList(this); TRACE_DEBUG(("drawingHandle = %d", GetThisObjectHandle() )); // // Tell other nodes that we are gone // if(GetMyWorkspace() != NULL && WasDeletedLocally()) { OnObjectDelete(); } if(m_hFont) { ::DeleteFont(m_hFont); m_hFont = NULL; } if (m_hFontThumb) { ::DeleteFont(m_hFontThumb); m_hFontThumb = NULL; } strTextArray.ClearOut(); strTextArray.RemoveAll(); } void TextObj::TextEditObj (TEXTPDU_ATTRIB* pEditAttrib ) { RECT rect; POSITION pos; POINT anchorPoint; LONG deltaX = 0; LONG deltaY = 0; TRACE_DEBUG(("TextEditObj drawingHandle = %d", GetThisObjectHandle() )); // // Was edited remotely // ClearEditionFlags(); // // Get the previous anchor point // GetAnchorPoint(&anchorPoint); // // Read attributes // m_dwChangedAttrib = pEditAttrib->attributesFlag; GetTextAttrib(pEditAttrib); // // Change the anchor point // if(HasAnchorPointChanged()) { { // // Get the delta from previous anchor point // deltaX -= anchorPoint.x; deltaY -= anchorPoint.y; // // Get the new anchor point // GetAnchorPoint(&anchorPoint); deltaX += anchorPoint.x; deltaY += anchorPoint.y; TRACE_DEBUG(("Delta (%d,%d)", deltaX , deltaY)); // // Was edited remotely // ClearEditionFlags(); } UnDraw(); GetRect(&rect); ::OffsetRect(&rect, deltaX, deltaY); SetRect(&rect); SetBoundsRect(&rect); } if(HasAnchorPointChanged() || HasFillColorChanged() || HasPenColorChanged() || HasFontChanged() || HasTextChanged()) { Draw(NULL); } else if(HasZOrderChanged()) { if(GetZOrder() == front) { g_pDraw->BringToTopSelection(FALSE, this); } else { g_pDraw->SendToBackSelection(FALSE, this); } } // // If it just select/unselected it // else if(HasViewStateChanged()) { ; // do nothing } // // If we have a valid font. // else if(GetFont()) { Draw(); } // // Reset all the attributes // ResetAttrib(); } void TextObj::GetTextAttrib(TEXTPDU_ATTRIB * pattributes) { if(HasPenColorChanged()) { SetPenColor(pattributes->textPenColor, TRUE); } if(HasFillColorChanged()) { SetFillColor(pattributes->textFillColor, TRUE); } if(HasViewStateChanged()) { // // If the other node is selecting the drawing or unselecting // if(pattributes->textViewState == selected_chosen) { SelectedRemotely(); } else if(pattributes->textViewState == unselected_chosen) { ClearSelectionFlags(); } SetViewState(pattributes->textViewState); } if(HasZOrderChanged()) { SetZOrder((ZOrder)pattributes->textZOrder); } if(HasAnchorPointChanged()) { SetAnchorPoint(pattributes->textAnchorPoint.x, pattributes->textAnchorPoint.y ); } if(HasFontChanged()) { UnDraw(); if(m_hFont) { ::DeleteFont(m_hFont); m_hFont = NULL; } m_hFont = ::CreateFontIndirect(&pattributes->textFont); if (!m_hFont) { // Could not create the font ERROR_OUT(("Failed to create font")); } if (m_hFontThumb) { ::DeleteFont(m_hFontThumb); m_hFontThumb = NULL; } m_hFontThumb = ::CreateFontIndirect(&pattributes->textFont); if (!m_hFontThumb) { // Could not create the font ERROR_OUT(("Failed to create thumbnail font")); } } int lines = 0; UINT maxString = 0; if(HasTextChanged()) { BYTE * pBuff = (BYTE *)&pattributes->textString; VARIABLE_STRING * pVarString = NULL; lines = pattributes->numberOfLines; int i; CHAR * cBuff = NULL; LPWSTR lpWideCharStr; for (i = 0; i < lines ; i++) { pVarString = (VARIABLE_STRING *) pBuff; lpWideCharStr = (LPWSTR)&pVarString->string; UINT strSize = 0; strSize= WideCharToMultiByte(CP_ACP, 0, lpWideCharStr, -1, NULL, 0, NULL, NULL ); // // Get the longest string // if(strSize > maxString) { maxString = strSize; } DBG_SAVE_FILE_LINE cBuff = new TCHAR[strSize]; WideCharToMultiByte(CP_ACP, 0, lpWideCharStr, -1, cBuff, strSize, NULL, NULL ); strTextArray.SetSize(i); strTextArray.SetAtGrow(i, cBuff ); delete cBuff; ASSERT(pVarString->header.start.y == i); pBuff += pVarString->header.len; } // // Calculate the rect // if(m_hFont) { // // Remove the old text before we paly with the text size // UnDraw(); g_pDraw->PrimeFont(g_pDraw->m_hDCCached, m_hFont, &m_textMetrics); g_pDraw->UnPrimeFont(g_pDraw->m_hDCCached); } } } void TextObj::SetTextAttrib(TEXTPDU_ATTRIB * pattributes) { if(HasPenColorChanged()) { GetPenColor(&pattributes->textPenColor); } if(HasFillColorChanged()) { GetFillColor(&pattributes->textFillColor); } if(HasViewStateChanged()) { pattributes->textViewState = GetViewState(); } if(HasZOrderChanged()) { pattributes->textZOrder = GetZOrder(); } if(HasAnchorPointChanged()) { GetAnchorPoint(&pattributes->textAnchorPoint); } if(HasFontChanged()) { ::GetObject(m_hFont, sizeof(LOGFONT), &pattributes->textFont); } if(HasTextChanged()) { BYTE * pBuff = (BYTE *)&pattributes->textString; VARIABLE_STRING * pVarString= NULL; LPWSTR lpWideCharStr; int size = strTextArray.GetSize(); int i; for (i = 0; i < size ; i++) { pVarString = (VARIABLE_STRING *)pBuff; lpWideCharStr = (LPWSTR)&pVarString->string; int strSize = 0; strSize= MultiByteToWideChar(CP_ACP, 0, strTextArray[i], -1, lpWideCharStr, 0)*sizeof(WCHAR); MultiByteToWideChar(CP_ACP, 0, strTextArray[i], -1, lpWideCharStr, strSize); pVarString->header.len = strSize + sizeof(VARIABLE_STRING_HEADER); pVarString->header.start.x = 0; // JOSEF change that pVarString->header.start.y = i; pBuff += pVarString->header.len; } pattributes->numberOfLines = size; // // Since we are sending text, need to send some font // ::GetObject(m_hFont, sizeof(LOGFONT), &pattributes->textFont); } } void TextObj::CreateTextPDU(ASN1octetstring_t *pData, UINT choice) { MSTextPDU * pTextPDU = NULL; UINT stringSize = 0; // Size of all the strings UNICODE int lines = 0; // Number of text lines // // Calculate the size of the whole pdu // ULONG length = 0; if(choice == textDeletePDU_chosen) { length = sizeof(MSTextDeletePDU); } else { // // Calculate the size of the text // if(HasTextChanged()) { int i; lines = strTextArray.GetSize(); for (i = 0; i < lines ; i++) { stringSize += MultiByteToWideChar(CP_ACP, 0, strTextArray[i], -1, NULL, 0) * sizeof(WCHAR); } } length = sizeof(MSTextPDU) + sizeof(VARIABLE_STRING_HEADER)* lines + stringSize; } DBG_SAVE_FILE_LINE pTextPDU = (MSTextPDU *) new BYTE[length]; // // PDU choice: create, edit delete // pTextPDU->header.nonStandardPDU = choice; // // This objects handle // pTextPDU->header.textHandle = GetThisObjectHandle(); TRACE_DEBUG(("Text >> Text handle = %d",pTextPDU->header.textHandle )); // // This objects workspacehandle // WorkspaceObj * pWorkspace = GetMyWorkspace(); ASSERT(pWorkspace); if(pWorkspace == NULL) { delete pTextPDU; pData->value = NULL; pData->length = 0; return; } pTextPDU->header.workspaceHandle = pWorkspace->GetThisObjectHandle(); TRACE_DEBUG(("Text >> Workspace handle = %d",pTextPDU->header.workspaceHandle )); if(choice != textDeletePDU_chosen) { // // Get all the attributes that changed // pTextPDU->attrib.attributesFlag = GetPresentAttribs(); SetTextAttrib(&pTextPDU->attrib); } // // Set the pointer for the data that is going to be encoded // pData->value = (ASN1octet_t *)pTextPDU; pData->length = length; } void TextObj::UnDraw(void) { RECT rect; GetBoundsRect(&rect); g_pDraw->InvalidateSurfaceRect(&rect,TRUE); } void TextObj::Draw(HDC hDC, BOOL thumbNail, BOOL bPrinting) { if(!bPrinting) { // // Don't draw anything if we don't belong in this workspace // if(GetWorkspaceHandle() != g_pCurrentWorkspace->GetThisObjectHandle()) { return; } } RECT clipBox; BOOL dbcsEnabled = GetSystemMetrics(SM_DBCSENABLED); INT *tabArray; UINT ch; int i,j; BOOL zoomed = g_pDraw->Zoomed(); int oldBkMode = 0; int iIndex = 0; POINT pointPos; int nLastTab; ABC abc; int iLength; TCHAR * strLine; MLZ_EntryOut(ZONE_FUNCTION, "DCWbGraphicText::Draw"); if(hDC == NULL) { hDC = g_pDraw->m_hDCCached; } // // Only draw anything if the bounding rectangle intersects the current // clip box. // if (::GetClipBox(hDC, &clipBox) == ERROR) { WARNING_OUT(("Failed to get clip box")); } // // Select the font. // if (thumbNail) { TRACE_MSG(("Using thumbnail font")); g_pDraw->PrimeFont(hDC, m_hFontThumb, &m_textMetrics); } else { TRACE_MSG(("Using standard font")); g_pDraw->PrimeFont(hDC, m_hFont, &m_textMetrics); } // // Set the color and mode for drawing. // COLORREF rgb; GetPenColor(&rgb); ::SetTextColor(hDC, SET_PALETTERGB(rgb)); // // Set the background to be transparent // oldBkMode = ::SetBkMode(hDC, TRANSPARENT); // // Calculate the bounding rectangle, accounting for the new font. // CalculateBoundsRect(); if (!::IntersectRect(&clipBox, &clipBox, &m_rect)) { TRACE_MSG(("No clip/bounds intersection")); return; } // // Get the start point for the text. // pointPos.x = m_rect.left + m_nKerningOffset; pointPos.y = m_rect.top; // // Loop through the text strings drawing each as we go. // for (iIndex = 0; iIndex < strTextArray.GetSize(); iIndex++) { // // Get a reference to the line to be printed for convenience. // strLine = (LPTSTR)strTextArray[iIndex]; iLength = lstrlen(strLine); // // Only draw the line if there are any characters in it. // if (iLength > 0) { if (zoomed) { // if new fails just skip it DBG_SAVE_FILE_LINE tabArray = new INT[iLength+1]; if( tabArray == NULL ) { ERROR_OUT(("Failed to allocate tabArray")); continue; } // We are zoomed. Must calculate char spacings // ourselfs so that they end up proportionally // in the right places. TabbedTextOut will not // do this right so we have to use ExtTextOut with // a tab array. // figure out tab array j = 0; nLastTab = 0; for (i=0; i < iLength; i++) { ch = strLine[(int)i]; //Don't worry about DBCS here... abc = GetTextABC(strLine, 0, i); if( j > 0 ) tabArray[j-1] = abc.abcB - nLastTab; nLastTab = abc.abcB; j++; } // Now, strip out any tab chars so they don't interact // in an obnoxious manner with the tab array we just // made and so they don't make ugly little // blocks when they are drawn. for (i=0; i < iLength; i++) { ch = strLine[(int)i]; if ((dbcsEnabled) && (IsDBCSLeadByte((BYTE)ch))) i++; else if(strLine[(int)i] == '\t') strLine[i] = ' '; // blow off tab, tab array // will compensate for this } // do it ::ExtTextOut(hDC, pointPos.x, pointPos.y, 0, NULL, strLine, iLength, tabArray); delete tabArray; } else { POINT ptPos; GetAnchorPoint(&ptPos); // Not zoomed, just do it ::TabbedTextOut(hDC, pointPos.x, pointPos.y, strLine, iLength, 0, NULL, ptPos.x); } } // // Move to the next line. // ASSERT(m_textMetrics.tmHeight != DBG_UNINIT); pointPos.y += (m_textMetrics.tmHeight); } // // Do NOT draw focus if clipboard or printing // if (WasSelectedLocally() && (hDC == g_pDraw->m_hDCCached)) { DrawRect(); } // // Restore the old background mode. // ::SetBkMode(hDC, oldBkMode); g_pDraw->UnPrimeFont(hDC); // // If we are drawing on top of a remote pointer, draw it. // BitmapObj* remotePointer = NULL; WBPOSITION pos = NULL; remotePointer = g_pCurrentWorkspace->RectHitRemotePointer(&m_rect, GetPenThickness()/2, NULL); while(remotePointer) { remotePointer->Draw(); remotePointer = g_pCurrentWorkspace->RectHitRemotePointer(&m_rect, GetPenThickness()/2, remotePointer->GetMyPosition()); } } void TextObj::SetPenColor(COLORREF rgb, BOOL isPresent) { ChangedPenColor(); m_bIsPenColorPresent = isPresent; if(!isPresent) { return; } m_penColor.rgbtRed = GetRValue(rgb); m_penColor.rgbtGreen = GetGValue(rgb); m_penColor.rgbtBlue = GetBValue(rgb); } BOOL TextObj::GetPenColor(COLORREF * rgb) { if(m_bIsPenColorPresent) { *rgb = RGB(m_penColor.rgbtRed, m_penColor.rgbtGreen, m_penColor.rgbtBlue); } return m_bIsPenColorPresent; } BOOL TextObj::GetPenColor(RGBTRIPLE* rgb) { if(m_bIsPenColorPresent) { *rgb = m_penColor; } return m_bIsPenColorPresent; } void TextObj::SetFillColor(COLORREF rgb, BOOL isPresent) { ChangedFillColor(); m_bIsFillColorPresent = isPresent; if(!isPresent) { return; } m_fillColor.rgbtRed = GetRValue(rgb); m_fillColor.rgbtGreen = GetGValue(rgb); m_fillColor.rgbtBlue = GetBValue(rgb); } BOOL TextObj::GetFillColor(COLORREF* rgb) { if(m_bIsFillColorPresent && rgb !=NULL) { *rgb = RGB(m_fillColor.rgbtRed, m_fillColor.rgbtGreen, m_fillColor.rgbtBlue); } return m_bIsFillColorPresent; } BOOL TextObj::GetFillColor(RGBTRIPLE* rgb) { if(m_bIsFillColorPresent && rgb!= NULL) { *rgb = m_fillColor; } return m_bIsFillColorPresent; } // // Get the encoded buffer for Drawing Create PDU // void TextObj::GetEncodedCreatePDU(ASN1_BUF *pBuf) { SIPDU *sipdu = NULL; DBG_SAVE_FILE_LINE sipdu = (SIPDU *) new BYTE[sizeof(SIPDU)]; if(sipdu) { sipdu->choice = siNonStandardPDU_chosen; CreateNonStandardPDU(&sipdu->u.siNonStandardPDU.nonStandardTransaction, NonStandardTextID); CreateTextPDU(&sipdu->u.siNonStandardPDU.nonStandardTransaction.data, textCreatePDU_chosen); ((MSTextPDU *)sipdu->u.siNonStandardPDU.nonStandardTransaction.data.value)->header.nonStandardPDU = textCreatePDU_chosen; ASN1_BUF encodedPDU; g_pCoder->Encode(sipdu, pBuf); if(sipdu->u.siNonStandardPDU.nonStandardTransaction.data.value) { delete sipdu->u.siNonStandardPDU.nonStandardTransaction.data.value; } delete sipdu; } else { TRACE_MSG(("Failed to create penMenu")); ::PostMessage(g_pMain->m_hwnd, WM_USER_DISPLAY_ERROR, WBFE_RC_WINDOWS, 0); } } void TextObj::SendTextPDU(UINT choice) { if(!g_pNMWBOBJ->CanDoText()) { return; } SIPDU *sipdu = NULL; DBG_SAVE_FILE_LINE sipdu = (SIPDU *) new BYTE[sizeof(SIPDU)]; if(sipdu) { sipdu->choice = siNonStandardPDU_chosen; CreateNonStandardPDU(&sipdu->u.siNonStandardPDU.nonStandardTransaction, NonStandardTextID); CreateTextPDU(&sipdu->u.siNonStandardPDU.nonStandardTransaction.data, choice); if(sipdu->u.siNonStandardPDU.nonStandardTransaction.data.value == NULL) { return; } T120Error rc = SendT126PDU(sipdu); if(rc == T120_NO_ERROR) { ResetAttrib(); SIPDUCleanUp(sipdu); } } else { TRACE_MSG(("Failed to create sipdu")); ::PostMessage(g_pMain->m_hwnd, WM_USER_DISPLAY_ERROR, WBFE_RC_WINDOWS, 0); } } // // UI Created a new Drawing Object // void TextObj::SendNewObjectToT126Apps(void) { SendTextPDU(textCreatePDU_chosen); } // // UI Edited the Drawing Object // void TextObj::OnObjectEdit(void) { g_bContentsChanged = TRUE; SendTextPDU(textEditPDU_chosen); } // // UI Deleted the Drawing Object // void TextObj::OnObjectDelete(void) { g_bContentsChanged = TRUE; SendTextPDU(textDeletePDU_chosen); } // // // Function: TextObj::SetFont // // Purpose: Set the font to be used for drawing // // void TextObj::SetFont(HFONT hFont) { MLZ_EntryOut(ZONE_FUNCTION, "TextObj::SetFont"); // Get the font details LOGFONT lfont; ::GetObject(hFont, sizeof(LOGFONT), &lfont); // // Pass the logical font into the SetFont() function // SetFont(&lfont); } // // // Function: TextObj::SetText // // Purpose: Set the text of the object // // void TextObj::SetText(TCHAR * strText) { // Remove all the current stored text strTextArray.SetSize(0); // Scan the text for carriage return and new-line characters int iNext = 0; int iLast = 0; int textSize = lstrlen(strText); TCHAR savedChar[1]; // // In this case, we don't know how many lines there will be. So we // use Add() from the StrArray class. // while (iNext < textSize) { // Find the next carriage return or line feed iNext += StrCspn(strText + iNext, "\r\n"); // Extract the text before the terminator // and add it to the current list of text lines. savedChar[0] = strText[iNext]; strText[iNext] = 0; strTextArray.Add((strText+iLast)); strText[iNext] = savedChar[0]; if (iNext < textSize) { // Skip the carriage return if (strText[iNext] == '\r') iNext++; // Skip a following new line (if there is one) if (strText[iNext] == '\n') iNext++; // Update the index of the start of the next line iLast = iNext; } } if(textSize) { // Calculate the bounding rectangle for the new text CalculateBoundsRect(); ChangedText(); } } // // // Function: TextObj::SetText // // Purpose: Set the text of the object // // void TextObj::SetText(const StrArray& _strTextArray) { // Scan the text for carriage return and new-line characters int iSize = _strTextArray.GetSize(); // // In this case we know how many lines, so set that # then use SetAt() // to stick text there. // strTextArray.RemoveAll(); strTextArray.SetSize(iSize); int iNext = 0; for ( ; iNext < iSize; iNext++) { strTextArray.SetAt(iNext, _strTextArray[iNext]); } // Calculate the new bounding rectangle CalculateBoundsRect(); } // // // Function: TextObj::SetFont(metrics) // // Purpose: Set the font to be used for drawing // // void TextObj::SetFont(LOGFONT *pLogFont, BOOL bReCalc ) { HFONT hNewFont; MLZ_EntryOut(ZONE_FUNCTION, "TextObj::SetFont"); // Ensure that the font can be resized by the zoom function // (proof quality prevents font scaling). pLogFont->lfQuality = DRAFT_QUALITY; //zap FontAssociation mode (bug 3258) pLogFont->lfClipPrecision |= CLIP_DFA_OVERRIDE; // Always work in cell coordinates to get scaling right TRACE_MSG(("Setting font height %d, width %d, face %s, family %d, precis %d", pLogFont->lfHeight,pLogFont->lfWidth,pLogFont->lfFaceName, pLogFont->lfPitchAndFamily, pLogFont->lfOutPrecision)); hNewFont = ::CreateFontIndirect(pLogFont); if (!hNewFont) { // Could not create the font ERROR_OUT(("Failed to create font")); DefaultExceptionHandler(WBFE_RC_WINDOWS, 0); return; } // We are now guaranteed to be able to delete the old font if (m_hFont != NULL) { DeleteFont(m_hFont); } m_hFont = hNewFont; // Calculate the line height for this font ASSERT(g_pDraw); g_pDraw->PrimeFont(g_pDraw->GetCachedDC(), m_hFont, &m_textMetrics); // Set up the thumbnail font, forcing truetype if not currently TT if (!(m_textMetrics.tmPitchAndFamily & TMPF_TRUETYPE)) { pLogFont->lfFaceName[0] = 0; pLogFont->lfOutPrecision = OUT_TT_PRECIS; TRACE_MSG(("Non-True type font")); } if (m_hFontThumb != NULL) { ::DeleteFont(m_hFontThumb); m_hFontThumb = NULL; } m_hFontThumb = ::CreateFontIndirect(pLogFont); if (!m_hFontThumb) { // Could not create the font ERROR_OUT(("Failed to create thumbnail font")); } // Calculate the bounding rectangle, accounting for the new font if( bReCalc ) CalculateBoundsRect(); ChangedFont(); g_pDraw->UnPrimeFont(g_pDraw->m_hDCCached); } // // // Function: TextObj::CalculateRect // // Purpose: Calculate the bounding rectangle of a portion of the object // // void TextObj::CalculateRect(int iStartX, int iStartY, int iStopX, int iStopY, LPRECT lprcResult) { RECT rcResult; RECT rcT; int iIndex; MLZ_EntryOut(ZONE_FUNCTION, "TextObj::CalculateRect"); // // NOTE: // We must use an intermediate rectangle, so as not to disturb the // contents of the passed-in one until done. lprcResult may be pointing // to the current bounds rect, and we call functions from here that // may need its current value. // // Initialize the result rectangle ::SetRectEmpty(&rcResult); if (!strTextArray.GetSize()) { // Text is empty goto DoneCalc; } // Allow for special limit values and ensure that the start and stop // character positions are in range. if (iStopY == LAST_LINE) { iStopY = strTextArray.GetSize() - 1; } iStopY = min(iStopY, strTextArray.GetSize() - 1); iStopY = max(iStopY, 0); if (iStopX == LAST_CHAR) { iStopX = lstrlen(strTextArray[iStopY]); } iStopX = min(iStopX, lstrlen(strTextArray[iStopY])); iStopX = max(iStopX, 0); // Loop through the text strings, adding each to the rectangle for (iIndex = iStartY; iIndex <= iStopY; iIndex++) { int iLeftX = ((iIndex == iStartY) ? iStartX : 0); int iRightX = ((iIndex == iStopY) ? iStopX : lstrlen(strTextArray[iIndex])); GetTextRectangle(iIndex, iLeftX, iRightX, &rcT); ::UnionRect(&rcResult, &rcResult, &rcT); } DoneCalc: *lprcResult = rcResult; } // // // Function: TextObj::CalculateBoundsRect // // Purpose: Calculate the bounding rectangle of the object // // void TextObj::CalculateBoundsRect(void) { // Set the new bounding rectangle CalculateRect(0, 0, LAST_CHAR, LAST_LINE, &m_rect); } // // // Function: TextObj::GetTextABC // // Purpose: Calculate the ABC numbers for a string of text // // COMMENT BY RAND: The abc returned is for the whole string, not just one // char. I.e, ABC.abcA is the offset to the first glyph in // the string, ABC.abcB is the sum of all of the glyphs and // ABC.abcC is the trailing space after the last glyph. // ABC.abcA + ABC.abcB + ABC.abcC is the total rendered // length including overhangs. // // Note - we never use the A spacing so it is always 0 // ABC TextObj::GetTextABC( LPCTSTR pText, int iStartX, int iStopX) { MLZ_EntryOut(ZONE_FUNCTION, "TextObj::GetTextABC"); ABC abcResult; HDC hDC; BOOL rc = FALSE; ABC abcFirst; ABC abcLast; BOOL zoomed = g_pDraw->Zoomed(); int nCharLast; int i; LPCTSTR pScanStr; ZeroMemory( (PVOID)&abcResult, sizeof abcResult ); ZeroMemory( (PVOID)&abcFirst, sizeof abcFirst ); ZeroMemory( (PVOID)&abcLast, sizeof abcLast ); // Get the standard size measure of the text LPCTSTR pABC = (pText + iStartX); int pABCLength = iStopX - iStartX; hDC = g_pDraw->GetCachedDC(); g_pDraw->PrimeFont(hDC, m_hFont, &m_textMetrics); // // We must temporarily unzoom if we are currently zoomed since the // weird Windows font handling will not give us the same answer for // the text extent in zoomed mode for some TrueType fonts // if (zoomed) { ::ScaleViewportExtEx(hDC, 1, g_pDraw->ZoomFactor(), 1, g_pDraw->ZoomFactor(), NULL); } DWORD size = ::GetTabbedTextExtent(hDC, pABC, pABCLength, 0, NULL); // We now have the advance width of the text abcResult.abcB = LOWORD(size); TRACE_MSG(("Basic text width is %d",abcResult.abcB)); // Allow for C space (or overhang) if (iStopX > iStartX) { if (m_textMetrics.tmPitchAndFamily & TMPF_TRUETYPE) { if(GetSystemMetrics( SM_DBCSENABLED )) { // have to handle DBCS on both ends if( IsDBCSLeadByte( (BYTE)pABC[0] ) ) { // pack multi byte char into a WORD for GetCharABCWidths WORD wMultiChar = MAKEWORD( pABC[1], pABC[0] ); rc = ::GetCharABCWidths(hDC, wMultiChar, wMultiChar, &abcFirst); } else { // first char is SBCS rc = ::GetCharABCWidths(hDC, pABC[0], pABC[0], &abcFirst ); } // Check for DBCS as last char. Have to scan whole string to be sure pScanStr = pABC; nCharLast = 0; for( i=0; iZoomFactor(), 1, g_pDraw->ZoomFactor(), 1, NULL); } TRACE_MSG(("Final text width is %d, C space %d",abcResult.abcB,abcResult.abcC)); return abcResult; } // // // Function: TextObj::GetTextRectangle // // Purpose: Calculate the bounding rectangle of a portion of the object // // void TextObj::GetTextRectangle(int iStartY, int iStartX, int iStopX, LPRECT lprc) { // ABC structures for text sizing ABC abcText1; ABC abcText2; int iLeftOffset = 0; MLZ_EntryOut(ZONE_FUNCTION, "TextObj::GetTextRectangle"); ASSERT(iStartY < strTextArray.GetSize()); // Here we calculate the width of the text glyphs in which we // are interested. In case there are tabs involved we must start // with position 0 and get two lengths then subtract them abcText1 = GetTextABC(strTextArray[iStartY], 0, iStopX); if (iStartX > 0) { // The third param used to be iStartX-1 which is WRONG. It // has to point to the first char pos past the string // we are using. abcText2 = GetTextABC(strTextArray[iStartY], 0, iStartX); // Just use B part for offset. Adding A snd/or C to it moves the update // rectangle too far to the right and clips the char iLeftOffset = abcText2.abcB; } else { ZeroMemory( &abcText2, sizeof abcText2 ); } // // We need to allow for A and C space in the bounding rectangle. Use // ABS function just to make sure we get a large enough rectangle. // // Move A and C from original offset calc to here for width of update // rectangle. Add in tmOverhang (non zero for non-tt fonts) to compensate // for the kludge in GetTextABC()....THIS EDITBOX CODE HAS GOT TO GO... abcText1.abcB = abcText1.abcB - iLeftOffset + abs(abcText2.abcA) + abs(abcText2.abcC) + abs(abcText1.abcA) + abs(abcText1.abcC) + m_textMetrics.tmOverhang; TRACE_DEBUG(("Left offset %d",iLeftOffset)); TRACE_DEBUG(("B width now %d",abcText1.abcB)); // Build the result rectangle. // Note that we never return an empty rectangle. This allows for the // fact that the Windows rectangle functions will ignore empty // rectangles completely. This would cause the bounding rectangle // calculation (for instance) to go wrong if the top or bottom lines // in a text object were empty. ASSERT(m_textMetrics.tmHeight != DBG_UNINIT); int iLineHeight = m_textMetrics.tmHeight + m_textMetrics.tmExternalLeading; lprc->left = 0; lprc->top = 0; lprc->right = max(1, abcText1.abcB); lprc->bottom = iLineHeight; ::OffsetRect(lprc, iLeftOffset, iLineHeight * iStartY); // rect is the correct width at this point but it might need to be schooted to // the left a bit to allow for kerning of 1st letter (bug 469) if( abcText1.abcA < 0 ) { ::OffsetRect(lprc, abcText1.abcA, 0); m_nKerningOffset = -abcText1.abcA; } else m_nKerningOffset = 0; POINT pt; GetAnchorPoint(&pt); ::OffsetRect(lprc, pt.x, pt.y); }