/* AbiWord * Copyright (C) 1998,1999 AbiSource, Inc. * Copyright (c) 2001,2002 Tomas Frydrych * * 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include #include #include "fp_TextRun.h" #include "fl_DocLayout.h" #include "fl_BlockLayout.h" #include "fp_Line.h" #include "pp_Property.h" #include "pp_AttrProp.h" #include "gr_Graphics.h" #include "pd_Document.h" #include "pd_Style.h" #include "gr_DrawArgs.h" #include "fv_View.h" #include "ut_debugmsg.h" #include "ut_assert.h" #include "ut_string.h" #include "ut_growbuf.h" #include "ut_units.h" #include "xap_EncodingManager.h" #include "ut_OverstrikingChars.h" #include "ut_Language.h" #ifdef BIDI_ENABLED #include "fribidi.h" #include "ut_contextGlyph.h" #include "ap_Prefs.h" #endif /*****************************************************************/ #ifdef BIDI_ENABLED //we cache pref value UseContextGlyphs; the following defines how often //to refresh the cache (in miliseconds) #define PREFS_REFRESH_MSCS 2000 //inicialise the static members of the class bool fp_TextRun::s_bUseContextGlyphs = true; bool fp_TextRun::s_bSaveContextGlyphs = false; bool fp_TextRun::s_bBidiOS = false; UT_Timer * fp_TextRun::s_pPrefsTimer = 0; UT_uint32 fp_TextRun::s_iClassInstanceCount = 0; UT_UCSChar getMirrorChar(UT_UCSChar c); #endif fp_TextRun::fp_TextRun(fl_BlockLayout* pBL, GR_Graphics* pG, UT_uint32 iOffsetFirst, UT_uint32 iLen, bool bLookupProperties) : fp_Run(pBL, pG, iOffsetFirst, iLen, FPRUN_TEXT), m_fPosition(TEXT_POSITION_NORMAL) { m_fDecorations = 0; m_iLineWidth = 0; m_bSquiggled = false; m_iSpaceWidthBeforeJustification = JUSTIFICATION_NOT_USED; //m_iOldSpaceWidthBeforeJustification = JUSTIFICATION_NOT_USED; m_pField = NULL; m_pLanguage = NULL; m_bIsOverhanging = false; #ifdef BIDI_ENABLED m_iDirection = FRIBIDI_TYPE_UNSET; //we will use this as an indication that the direction property has not been yet set //normal values are -1,0,1 (neutral, ltr, rtl) m_iDirOverride = FRIBIDI_TYPE_UNSET; //no override by default #endif if (bLookupProperties) { lookupProperties(); } m_bRecalcWidth = true; //m_iOldLen = 0; //m_pOldScreenFont = 0; #ifdef BIDI_ENABLED //m_pOldNext = NULL; //m_pOldPrev = NULL; m_bRefreshDrawBuffer = true; //in order to be able to print rtl runs on GUI that does not support this, //we will need to make a copy of the run and strrev it; this is a static //buffer shared by all the instances; if(!s_pPrefsTimer) { XAP_App::getApp()->getPrefsValueBool((XML_Char*)XAP_PREF_KEY_UseContextGlyphs, &s_bUseContextGlyphs); XAP_App::getApp()->getPrefsValueBool((XML_Char*)XAP_PREF_KEY_SaveContextGlyphs, &s_bSaveContextGlyphs); s_pPrefsTimer = UT_Timer::static_constructor(_refreshPrefs, NULL); if(s_pPrefsTimer) s_pPrefsTimer->set(PREFS_REFRESH_MSCS); } m_pSpanBuff = new UT_UCSChar[m_iLen + 1]; m_iSpanBuffSize = m_iLen; UT_ASSERT(m_pSpanBuff); if(!s_iClassInstanceCount) { s_bBidiOS = XAP_App::getApp()->theOSHasBidiSupport(); UT_DEBUGMSG(("fp_TextRun size is %d\n",sizeof(fp_TextRun) )); } s_iClassInstanceCount++; #endif } fp_TextRun::~fp_TextRun() { #ifdef BIDI_ENABLED --s_iClassInstanceCount; if(!s_iClassInstanceCount) { DELETEP(s_pPrefsTimer); } delete[] m_pSpanBuff; #endif } bool fp_TextRun::hasLayoutProperties(void) const { return true; } void fp_TextRun::lookupProperties(void) { clearScreen(); const PP_AttrProp * pSpanAP = NULL; const PP_AttrProp * pBlockAP = NULL; const PP_AttrProp * pSectionAP = NULL; // TODO do we care about section-level inheritance? m_pBL->getSpanAttrProp(m_iOffsetFirst,false,&pSpanAP); m_pBL->getAttrProp(&pBlockAP); static_cast(m_pBL)->getField(m_iOffsetFirst,m_pField); // look for fonts in this DocLayout's font cache FL_DocLayout * pLayout = m_pBL->getDocLayout(); PD_Document * pDoc = m_pBL->getDocument(); const PP_PropertyTypeColor *p_color = (const PP_PropertyTypeColor *)PP_evalPropertyType("color",pSpanAP,pBlockAP,pSectionAP, Property_type_color, pDoc, true); UT_ASSERT(p_color); m_colorFG = p_color->getColor(); getHighlightColor(); getPageColor(); const XML_Char* pszStyle = NULL; if(pSpanAP && pSpanAP->getAttribute(PT_STYLE_ATTRIBUTE_NAME, pszStyle)) { PD_Style *pStyle = NULL; pDoc->getStyle((const char*) pszStyle, &pStyle); if(pStyle) pStyle->used(1); } const XML_Char *pszFontStyle = PP_evalProperty("font-style",pSpanAP,pBlockAP,pSectionAP, pDoc, true); m_bIsOverhanging = (pszFontStyle && !UT_strcmp(pszFontStyle, "italic")); const XML_Char *pszDecor = PP_evalProperty("text-decoration",pSpanAP,pBlockAP,pSectionAP, pDoc, true); /* TODO map line width to a property, not a hard-coded value */ m_iLineWidth = m_pG->convertDimension("0.8pt"); m_fDecorations = 0; XML_Char* p; if (!UT_cloneString((char *&)p, pszDecor)) { // TODO outofmem } UT_ASSERT(p || !pszDecor); XML_Char* q = strtok(p, " "); while (q) { if (0 == UT_strcmp(q, "underline")) { m_fDecorations |= TEXT_DECOR_UNDERLINE; } else if (0 == UT_strcmp(q, "overline")) { m_fDecorations |= TEXT_DECOR_OVERLINE; } else if (0 == UT_strcmp(q, "line-through")) { m_fDecorations |= TEXT_DECOR_LINETHROUGH; } else if (0 == UT_strcmp(q, "topline")) { m_fDecorations |= TEXT_DECOR_TOPLINE; } else if (0 == UT_strcmp(q, "bottomline")) { m_fDecorations |= TEXT_DECOR_BOTTOMLINE; } q = strtok(NULL, " "); } free(p); const XML_Char * pszPosition = PP_evalProperty("text-position",pSpanAP,pBlockAP,pSectionAP, pDoc, true); if (0 == UT_strcmp(pszPosition, "superscript")) { m_fPosition = TEXT_POSITION_SUPERSCRIPT; } else if (0 == UT_strcmp(pszPosition, "subscript")) { m_fPosition = TEXT_POSITION_SUBSCRIPT; } else m_fPosition = TEXT_POSITION_NORMAL; GR_Font * pFont; pFont = pLayout->findFont(pSpanAP,pBlockAP,pSectionAP, FL_DocLayout::FIND_FONT_AT_SCREEN_RESOLUTION); if (m_pScreenFont != pFont) { m_bRecalcWidth = true; m_pScreenFont = pFont; m_iAscent = m_pG->getFontAscent(m_pScreenFont); m_iDescent = m_pG->getFontDescent(m_pScreenFont); m_iHeight = m_pG->getFontHeight(m_pScreenFont); } pFont = pLayout->findFont(pSpanAP,pBlockAP,pSectionAP, FL_DocLayout::FIND_FONT_AT_LAYOUT_RESOLUTION); if (m_pLayoutFont != pFont) { m_bRecalcWidth = true; m_pLayoutFont = pFont; m_iAscentLayoutUnits = m_pG->getFontAscent(m_pLayoutFont); UT_ASSERT(m_iAscentLayoutUnits); m_iDescentLayoutUnits = m_pG->getFontDescent(m_pLayoutFont); m_iHeightLayoutUnits = m_pG->getFontHeight(m_pLayoutFont); } #if 1 m_pG->setFont(m_pScreenFont); #endif //set the language member UT_Language *lls = new UT_Language; const XML_Char * pszLanguage = PP_evalProperty("lang",pSpanAP,pBlockAP,pSectionAP, pDoc, true); const XML_Char * pszOldLanguage = m_pLanguage; m_pLanguage = lls->getPropertyFromProperty(pszLanguage); if(pszOldLanguage && m_pLanguage != pszOldLanguage) m_pBL->getDocLayout()->queueBlockForBackgroundCheck((UT_uint32) FL_DocLayout::bgcrSpelling, m_pBL); //UT_DEBUGMSG(("fp_TextRun::lookupProperties: m_pLanguage = %s\n", m_pLanguage)); delete lls; #ifdef BIDI_ENABLED #ifdef SMART_RUN_MERGING FriBidiCharType iOldOverride = m_iDirOverride; #endif FriBidiCharType iNewOverride; const XML_Char *pszDirection = PP_evalProperty("dir-override",pSpanAP,pBlockAP,pSectionAP, pDoc, true); // the way MS Word handles bidi is peculiar and requires that we allow // temporarily a non-standard value for the dir-override property // called "nobidi" #if 0 bool bMSWordNoBidi = false; #endif if(!pszDirection) iNewOverride = FRIBIDI_TYPE_UNSET; else if(!UT_stricmp(pszDirection, "ltr")) iNewOverride = FRIBIDI_TYPE_LTR; else if(!UT_stricmp(pszDirection, "rtl")) iNewOverride = FRIBIDI_TYPE_RTL; #if 0 else if(!UT_stricmp(pszDirection, "nobidi")) bMSWordNoBidi = true; #endif else iNewOverride = FRIBIDI_TYPE_UNSET; #ifdef SMART_RUN_MERGING /* OK, if the previous direction override was strong, and the new one is not (i.e., if we are called because the user removed an override from us) we have to split this run into chunks with uniform Unicode directional property if the previous direction override was not strong, and the current one is, we have to break this run's neighbours */ if(iNewOverride == FRIBIDI_TYPE_UNSET && iOldOverride != FRIBIDI_TYPE_UNSET) { // we have to do this without applying the new override otherwise the // LTR and RTL run counters of the present line will be messed up; // breakMeAtDirBoundaries will take care of applying the new override breakMeAtDirBoundaries(iNewOverride); } else if(iNewOverride != FRIBIDI_TYPE_UNSET && iOldOverride == FRIBIDI_TYPE_UNSET) { // first we have to apply the new override setDirection(FRIBIDI_TYPE_UNSET, iNewOverride); // now do the breaking breakNeighborsAtDirBoundaries(); } else #endif setDirection(FRIBIDI_TYPE_UNSET, iNewOverride); // now we should have a valid value for both direction and override, // except in the MSWord specific case /* the problem with the following code is, that although it works quite nicely, it leaves incorrectly bracketed record in the undo, and if the user gets to a point when undo actually tries to restore the nobidi property, it leads to infinite recursion. I have tried a number of things, but did not succeed in finding a way to get around that, so for the time being this is off and the MSWord importer sets directly "ltr". (This is a nuisance, since it means unnecessary use of the dir-override property.) */ #if 0 if(bMSWordNoBidi) { // the nobidi override is used by the importer to indicate that no bidi // algortithm should be applied to this text; we will only respect this // if the current run is weak and translate it to LTR (only happens in LTR // contexts) this effectively means that all numbers in LTR context imported // from Word will be overriden to strong LTR const XML_Char * prop[] = {NULL, NULL, 0}; const XML_Char direction[] = "dir-override"; const XML_Char ltr[] = "ltr"; const XML_Char nbidi[] = "nobidi"; prop[0] = (XML_Char*) &direction; UT_uint32 offset = m_pBL->getPosition() + m_iOffsetFirst; if(FRIBIDI_IS_WEAK(m_iDirection)) { prop[1] = (XML_Char*) <r; m_iDirOverride = FRIBIDI_TYPE_LTR; // this might have resulted in change of overall direction, so // make sure that all is OK setDirection(FRIBIDI_TYPE_UNSET); bool bRes = getBlock()->getDocument()->changeSpanFmt(PTC_AddFmt,offset,offset + m_iLen,NULL,prop); UT_DEBUGMSG(("fp_TextRun::lookupProperties: changing \"nobidi\" to \"ltr\", dir %d, bRes %d\n", m_iDirection, bRes)); } else { UT_DEBUGMSG(("fp_TextRun::lookupProperties: removing \"nobidi\", dir %d\n", m_iDirection)); prop[1] = (XML_Char*) &nbidi; m_iDirOverride = FRIBIDI_TYPE_UNSET; // no need to call setDirection, since the overall direction remains the same //setDirection(FRIBIDI_TYPE_UNSET); getBlock()->getDocument()->changeSpanFmt(PTC_RemoveFmt,offset,offset + m_iLen,NULL,prop); } } #endif xxx_UT_DEBUGMSG(("TextRun::lookupProperties: m_iDirection=%d, m_iDirOverride=%d\n", m_iDirection, m_iDirOverride)); #endif } bool fp_TextRun::canBreakAfter(void) const { const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 offset = m_iOffsetFirst; UT_uint32 len = m_iLen; bool bContinue = true; if (len > 0) { while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); UT_ASSERT(lenSpan>0); if (len <= lenSpan) { UT_ASSERT(len>0); if (XAP_EncodingManager::get_instance()->can_break_at(pSpan[len-1])) { return true; } bContinue = false; } else { offset += lenSpan; len -= lenSpan; } } } else if (!m_pNext) { return true; } if (m_pNext) { return m_pNext->canBreakBefore(); } return false; } bool fp_TextRun::canBreakBefore(void) const { const UT_UCSChar* pSpan; UT_uint32 lenSpan; if (m_iLen > 0) { if (m_pBL->getSpanPtr(m_iOffsetFirst, &pSpan, &lenSpan)) { UT_ASSERT(lenSpan>0); if (XAP_EncodingManager::get_instance()->can_break_at(pSpan[0])) { return true; } } } else { if (m_pNext) { return m_pNext->canBreakBefore(); } else { return true; } } return false; } bool fp_TextRun::alwaysFits(void) const { const UT_UCSChar* pSpan; UT_uint32 lenSpan; // TODO we need to fix this code to use getSpanPtr the way it is used elsewhere in this file. if (m_iLen > 0) { if (m_pBL->getSpanPtr(m_iOffsetFirst, &pSpan, &lenSpan)) { UT_ASSERT(lenSpan>0); for (UT_uint32 i=0; igetCharWidths()->getCharWidthsLayoutUnits(); UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); UT_sint32 iLeftWidth = 0; UT_sint32 iRightWidth = m_iWidthLayoutUnits; si.iOffset = -1; const UT_UCSChar* pSpan; UT_uint32 lenSpan; UT_uint32 offset = m_iOffsetFirst; UT_uint32 len = m_iLen; bool bContinue = true; while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); UT_ASSERT(lenSpan>0); if (lenSpan > len) { lenSpan = len; } for (UT_uint32 i=0; ican_break_at(pSpan[i]) && pSpan[i]!=UCS_SPACE) || ( (UCS_SPACE == pSpan[i]) #else if ( (XAP_EncodingManager::get_instance()->can_break_at(pSpan[i]) #endif && ((i + offset) != (m_iOffsetFirst + m_iLen - 1)) ) || bForce ) { if (iLeftWidth - pCharWidths[i + offset] <= iMaxLeftWidth) { si.iLeftWidth = iLeftWidth; si.iRightWidth = iRightWidth; si.iOffset = i + offset; } else { bContinue = false; break; } } } if (len <= lenSpan) { bContinue = false; } else { offset += lenSpan; len -= lenSpan; } } if ( (si.iOffset == -1) || (si.iLeftWidth == m_iWidthLayoutUnits) ) { // there were no split points which fit. return false; } return true; } void fp_TextRun::mapXYToPosition(UT_sint32 x, UT_sint32 /*y*/, PT_DocPosition& pos, bool& bBOL, bool& bEOL) { #ifdef BIDI_ENABLED FriBidiCharType iVisDirection = getVisDirection(); FriBidiCharType iDomDirection = m_pBL->getDominantDirection(); #endif if (x <= 0) { #ifdef BIDI_ENABLED if(iVisDirection == FRIBIDI_TYPE_RTL) { pos = m_pBL->getPosition() + m_iOffsetFirst + m_iLen; if(iDomDirection == FRIBIDI_TYPE_RTL) { bEOL = true; bBOL = false; } else { bEOL = false; bBOL = true; } } else #endif { pos = m_pBL->getPosition() + m_iOffsetFirst; // don't set bBOL to false here bEOL = false; } #if 0//def BIDI_ENABLED // if we are in rtl run, we want to move past this character if(iVisDirection == FRIBIDI_TYPE_RTL) pos++; #endif return; } if (x >= m_iWidth) { #ifdef BIDI_ENABLED if(iVisDirection == FRIBIDI_TYPE_RTL) { pos = m_pBL->getPosition() + m_iOffsetFirst; if(iDomDirection == FRIBIDI_TYPE_RTL) { bEOL = false; bBOL = true; } else { bEOL = true; bBOL = false; } } else #endif { pos = m_pBL->getPosition() + m_iOffsetFirst + m_iLen; // Setting bEOL fixes bug 1149. But bEOL has been set in the // past - probably somewhere else, so this is not necessarily // the correct place to do it. 2001.02.25 jskov bEOL = true; } #if 0 //def BIDI_ENABLED // if we are in rtl run, we want to move before this character if(iVisDirection == FRIBIDI_TYPE_RTL && pos > 0) pos--; #endif return; } const UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); const UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); // catch the case of a click directly on the left half of the first character in the run if (x < (pCharWidths[m_iOffsetFirst] / 2)) { #ifdef BIDI_ENABLED pos = m_pBL->getPosition() + getOffsetFirstVis(); // if this character is RTL then clicking on the left side // means the user wants to postion the caret _after_ this char if(iVisDirection == FRIBIDI_TYPE_RTL) pos++; #else pos = m_pBL->getPosition() + m_iOffsetFirst; #endif bBOL = false; bEOL = false; return; } UT_sint32 iWidth = 0; for (UT_uint32 i=m_iOffsetFirst; i<(m_iOffsetFirst + m_iLen); i++) { #ifdef BIDI_ENABLED // i represents VISUAL offset but the CharWidths array uses logical order of indexing UT_uint32 iLog = getOffsetLog(i); #else UT_uint32 iLog = i; #endif iWidth += pCharWidths[iLog]; if (iWidth > x) { if (((iWidth - x) <= (pCharWidths[iLog] / 2) #ifdef BIDI_ENABLED && (iVisDirection == FRIBIDI_TYPE_LTR)) || (((iWidth - x) > (pCharWidths[iLog] / 2) && (iVisDirection == FRIBIDI_TYPE_RTL)) #endif )) { iLog++; } // NOTE: this allows inserted text to be coalesced in the PT bEOL = true; pos = m_pBL->getPosition() + iLog; return; } } UT_DEBUGMSG(("fp_TextRun::mapXYToPosition: x %d, m_iWidth %d\n", x,m_iWidth)); UT_ASSERT(UT_SHOULD_NOT_HAPPEN); } void fp_TextRun::findPointCoords(UT_uint32 iOffset, UT_sint32& x, UT_sint32& y, UT_sint32& x2, UT_sint32& y2, UT_sint32& height, bool& bDirection) { UT_sint32 xoff; UT_sint32 yoff; #ifdef BIDI_ENABLED UT_sint32 xoff2; UT_sint32 yoff2; UT_sint32 xdiff = 0; #endif UT_ASSERT(m_pLine); m_pLine->getOffsets(this, xoff, yoff); const UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); const UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); UT_uint32 offset = UT_MIN(iOffset, m_iOffsetFirst + m_iLen); for (UT_uint32 i=m_iOffsetFirst; igetVisDirection(); pRun->getLine()->getOffsets(pRun, xoff2, yoff2); // if the next run is the end of paragraph marker, // we need to derive yoff2 from the offset of this // run instead of the marker if(pRun->getType() == FPRUN_ENDOFPARAGRAPH) yoff2 = yoff; } } if(iDirection == FRIBIDI_TYPE_RTL) //#TF rtl run { x = xoff + m_iWidth - xdiff; //we want the caret right of the char } else { x = xoff + xdiff; } if(pRun && (iNextDir != iDirection)) //followed by run of different direction, have to split caret { x2 = (iNextDir == FRIBIDI_TYPE_LTR) ? xoff2 : xoff2 + pRun->getWidth(); y2 = yoff2; } else { x2 = x; y2 = yoff; } bDirection = (iDirection != FRIBIDI_TYPE_LTR); #if 0 //def DEBUG char rtl[] = "RTL"; char ltr[] = "LTR"; char other[]= "other"; char *d, *n; switch(iDirection) { case FRIBIDI_TYPE_LTR: d = ltr; break; case FRIBIDI_TYPE_RTL: d = rtl; break; default: d = other; } switch(iNextDir) { case FRIBIDI_TYPE_LTR: n = ltr; break; case FRIBIDI_TYPE_RTL: n = rtl; break; default: n = other; } xxx_UT_DEBUGMSG(("fp_TextRun::findPointCoords: (0x%x) x,y,x2,y2=[%d, %d, %d, %d]\n" " xoff %d, xoff2 %d, xdiff %d, m_iWidth %d\n" " iDirection %s, iNextDir %s, pRun 0x%x\n", this, x, y, x2, y2, xoff, xoff2, xdiff, m_iWidth, d, n,pRun)); #endif #else // ! BIDI_ENABLED x = xoff; #endif y = yoff; height = m_iHeight; } bool fp_TextRun::canMergeWithNext(void) { if (!m_pNext || !m_pLine || m_pNext->getType() != FPRUN_TEXT || !m_pNext->getLine()) { return false; } fp_TextRun* pNext = static_cast(m_pNext); if ( (pNext->m_iOffsetFirst != (m_iOffsetFirst + m_iLen)) || (pNext->m_fDecorations != m_fDecorations) || (pNext->m_pScreenFont != m_pScreenFont) || (pNext->m_pLayoutFont != m_pLayoutFont) || (m_iHeight != pNext->m_iHeight) // runs that have been justified cannot be merged together, because // this would result in the screen text being out of sync with the actual // character positions || (m_iSpaceWidthBeforeJustification != JUSTIFICATION_NOT_USED) || (pNext->m_iSpaceWidthBeforeJustification != JUSTIFICATION_NOT_USED) || (pNext->m_pField != m_pField) || (pNext->m_pLanguage != m_pLanguage) //this is not a bug || (pNext->m_colorFG != m_colorFG) || (pNext->m_colorHL != m_colorHL) || (pNext->m_fPosition != m_fPosition) #ifdef BIDI_ENABLED #ifdef SMART_RUN_MERGING || (pNext->getVisDirection() != getVisDirection()) // we also want to test the override, because we do not want runs that have the same // visual direction but different override merged #else || (pNext->m_iDirection != m_iDirection) //#TF cannot merge runs of different direction of writing #endif || (pNext->m_iDirOverride != m_iDirOverride) #endif ) { return false; } return true; } void fp_TextRun::mergeWithNext(void) { UT_ASSERT(m_pNext && (m_pNext->getType() == FPRUN_TEXT)); UT_ASSERT(m_pLine); UT_ASSERT(m_pNext->getLine()); fp_TextRun* pNext = (fp_TextRun*) m_pNext; UT_ASSERT(pNext->m_iOffsetFirst == (m_iOffsetFirst + m_iLen)); UT_ASSERT(pNext->m_pScreenFont == m_pScreenFont); // is this legal? UT_ASSERT(pNext->m_pLayoutFont == m_pLayoutFont); // is this legal? UT_ASSERT(pNext->m_fDecorations == m_fDecorations); UT_ASSERT(m_iAscent == pNext->m_iAscent); UT_ASSERT(m_iDescent == pNext->m_iDescent); UT_ASSERT(m_iHeight == pNext->m_iHeight); UT_ASSERT(m_iLineWidth == pNext->m_iLineWidth); UT_ASSERT(m_pLanguage == pNext->m_pLanguage); //this is not a bug UT_ASSERT(m_fPosition == pNext->m_fPosition); #ifdef BIDI_ENABLED #ifndef SMART_RUN_MERGING UT_ASSERT(m_iDirection == pNext->m_iDirection); //#TF #endif UT_ASSERT(m_iDirOverride == pNext->m_iDirOverride); //#TF #endif //UT_ASSERT(m_iSpaceWidthBeforeJustification == pNext->m_iSpaceWidthBeforeJustification); m_pField = pNext->m_pField; m_iWidth += pNext->m_iWidth; m_iWidthLayoutUnits += pNext->m_iWidthLayoutUnits; #ifdef BIDI_ENABLED UT_DEBUGMSG(("fp_TextRun::mergeWithNext\n")); // first of all, make sure the X coordinance of the merged run is correct if(m_iX > pNext->m_iX) m_iX = pNext->m_iX; // join the two span buffers; this will save us refreshing the draw buffer // which is very expensive // however, first make sure that the buffers are uptodate (_refreshDrawBuffer // decides whether this is needed or not) _refreshDrawBuffer(); pNext->_refreshDrawBuffer(); // we need to take into consideration whether this run has been reversed // in which case the order of the concating needs to be reversed too FriBidiCharType iVisDirection = getVisDirection(); bool bReverse = (!s_bBidiOS && iVisDirection == FRIBIDI_TYPE_RTL) || (s_bBidiOS && m_iDirOverride == FRIBIDI_TYPE_LTR && m_iDirection == FRIBIDI_TYPE_RTL); if((m_iSpanBuffSize <= m_iLen + pNext->m_iLen) || (bReverse && (m_iLen > pNext->m_iLen))) { UT_DEBUGMSG(("fp_TextRun::mergeWithNext: reallocating span buffer\n")); m_iSpanBuffSize = m_iLen + pNext->m_iLen + 1; UT_UCSChar * pSB = new UT_UCSChar[m_iSpanBuffSize]; UT_ASSERT(pSB); if(bReverse) { UT_UCS_strncpy(pSB, pNext->m_pSpanBuff, pNext->m_iLen); UT_UCS_strncpy(pSB + pNext->m_iLen,m_pSpanBuff, m_iLen); } else { UT_UCS_strncpy(pSB,m_pSpanBuff, m_iLen); UT_UCS_strncpy(pSB + m_iLen, pNext->m_pSpanBuff, pNext->m_iLen); } *(pSB + m_iLen + pNext->m_iLen) = 0; delete [] m_pSpanBuff; m_pSpanBuff = pSB; } else { UT_DEBUGMSG(("fp_TextRun::mergeWithNext: reusing existin span buffer\n")); if(bReverse) { // can only shift the text directly in the existing buffer if // m_iLen <= pNext->m_iLen UT_ASSERT(m_iLen <= pNext->m_iLen); UT_UCS_strncpy(m_pSpanBuff + pNext->m_iLen, m_pSpanBuff, m_iLen); UT_UCS_strncpy(m_pSpanBuff, pNext->m_pSpanBuff, pNext->m_iLen); } else { UT_UCS_strncpy(m_pSpanBuff + m_iLen, pNext->m_pSpanBuff, pNext->m_iLen); } *(m_pSpanBuff + m_iLen + pNext->m_iLen) = 0; } #endif m_iLen += pNext->m_iLen; m_bDirty = m_bDirty || pNext->m_bDirty; m_pNext = pNext->getNext(); if (m_pNext) { // do not mark anything dirty m_pNext->setPrev(this, false); } #if 0 // this is not needed, since we have already added the widths // of the two runs #ifdef BIDI_ENABLED //_addupCharWidths(); #else m_bRecalcWidth = true; recalcWidth(); #endif #endif pNext->getLine()->removeRun(pNext, false); #ifdef SMART_RUN_MERGING // if appending a strong run onto a weak one, make sure the overall direction // is that of the strong run, and tell the line about this, since the call // to removeRun above decreased the line's direction counter if(!FRIBIDI_IS_STRONG(m_iDirection) && FRIBIDI_IS_STRONG(pNext->m_iDirection)) { m_iDirection = pNext->m_iDirection; m_pLine->addDirectionUsed(m_iDirection); } #endif delete pNext; } bool fp_TextRun::split(UT_uint32 iSplitOffset) { UT_ASSERT(iSplitOffset >= m_iOffsetFirst); UT_ASSERT(iSplitOffset < (m_iOffsetFirst + m_iLen)); #ifdef BIDI_ENABLED FriBidiCharType iVisDirection = getVisDirection(); #endif fp_TextRun* pNew = new fp_TextRun(m_pBL, m_pG, iSplitOffset, m_iLen - (iSplitOffset - m_iOffsetFirst), false); UT_ASSERT(pNew); pNew->m_pScreenFont = this->m_pScreenFont; pNew->m_pLayoutFont = this->m_pLayoutFont; pNew->m_fDecorations = this->m_fDecorations; pNew->m_colorFG = this->m_colorFG; pNew->m_pField = this->m_pField; pNew->m_fPosition = this->m_fPosition; pNew->m_iAscent = this->m_iAscent; pNew->m_iDescent = this->m_iDescent; pNew->m_iHeight = this->m_iHeight; pNew->m_iAscentLayoutUnits = this->m_iAscentLayoutUnits; UT_ASSERT(pNew->m_iAscentLayoutUnits); pNew->m_iDescentLayoutUnits = this->m_iDescentLayoutUnits; pNew->m_iHeightLayoutUnits = this->m_iHeightLayoutUnits; pNew->m_iLineWidth = this->m_iLineWidth; pNew->m_bDirty = this->m_bDirty; pNew->m_pLanguage = this->m_pLanguage; #ifdef BIDI_ENABLED pNew->m_iDirection = this->m_iDirection; //#TF pNew->m_iDirOverride = this->m_iDirOverride; #ifdef SMART_RUN_MERGING // set the visual direction to same as that of the old run pNew->setVisDirection(iVisDirection); #endif #endif // need to set this, so the split run could reset its justification correctly pNew->m_iSpaceWidthBeforeJustification = this->m_iSpaceWidthBeforeJustification; pNew->m_pHyperlink = this->getHyperlink(); pNew->m_pPrev = this; pNew->m_pNext = this->m_pNext; if (m_pNext) { // do not mark anything dirty m_pNext->setPrev(pNew, false); } m_pNext = pNew; m_iLen = iSplitOffset - m_iOffsetFirst; m_pLine->insertRunAfter(pNew, this); #ifdef BIDI_ENABLED // first of all, split the span buffer, this will save us refreshing it // which is very expensive (see notes on the mergeWithNext()) // we have a dilema here, either we can leave the span buffer for this run // of the size it is now, or we delete it and allocate a shorter one // we will use the first option, the extra delete/new pair should not // cost us too much time m_iSpanBuffSize = m_iLen + 1; UT_UCSChar * pSB = new UT_UCSChar[m_iSpanBuffSize]; if((!s_bBidiOS && iVisDirection == FRIBIDI_TYPE_RTL) || (s_bBidiOS && m_iDirOverride == FRIBIDI_TYPE_LTR && m_iDirection == FRIBIDI_TYPE_RTL) ) { UT_UCS_strncpy(pSB, m_pSpanBuff + pNew->m_iLen, m_iLen); UT_UCS_strncpy(pNew->m_pSpanBuff, m_pSpanBuff, pNew->m_iLen); } else { UT_UCS_strncpy(pSB, m_pSpanBuff, m_iLen); UT_UCS_strncpy(pNew->m_pSpanBuff, m_pSpanBuff + m_iLen, pNew->m_iLen); } pSB[m_iLen] = 0; pNew->m_pSpanBuff[pNew->m_iLen] = 0; delete[] m_pSpanBuff; m_pSpanBuff = pSB; // we will use the _addupCharWidths() function here instead of recalcWidth(), since when // a run is split the info in the block's char-width array is not affected, so we do not //have to recalculate these _addupCharWidths(); pNew->_addupCharWidths(); #else #if 0 m_bRecalcWidth = true; m_iWidth = simpleRecalcWidth(Width_type_display); m_iWidthLayoutUnits = simpleRecalcWidth(Width_type_layout_units); pNew->m_iWidth = pNew->simpleRecalcWidth(Width_type_display); pNew->m_iWidthLayoutUnits = pNew->simpleRecalcWidth(Width_type_layout_units); #else _addupCharWidths(); pNew->_addupCharWidths(); #endif #endif #ifdef BIDI_ENABLED //bool bDomDirection = m_pBL->getDominantDirection(); if(iVisDirection == FRIBIDI_TYPE_LTR) { pNew->m_iX = m_iX + m_iWidth; } else { pNew->m_iX = m_iX; m_iX += pNew->m_iWidth; } #else pNew->m_iX = m_iX + m_iWidth; #endif pNew->m_iY = m_iY; return true; } void fp_TextRun::_fetchCharWidths(GR_Font* pFont, UT_uint16* pCharWidths) { UT_ASSERT(pCharWidths); UT_ASSERT(pFont); const UT_UCSChar* pSpan; UT_uint32 lenSpan; UT_uint32 offset = m_iOffsetFirst; UT_uint32 len = m_iLen; bool bContinue = true; while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); UT_ASSERT(lenSpan>0); m_pG->setFont(pFont); if (len <= lenSpan) { m_pG->measureString(pSpan, 0, len, pCharWidths + offset); bContinue = false; } else { m_pG->measureString(pSpan, 0, lenSpan, pCharWidths + offset); offset += lenSpan; len -= lenSpan; } } } void fp_TextRun::fetchCharWidths(fl_CharWidths * pgbCharWidths) { if (m_iLen == 0) { return; } #ifdef BIDI_ENABLED if(!s_bUseContextGlyphs) { // if we are using context glyphs we will do nothing, since this // will be done by recalcWidth() #endif UT_uint16* pCharWidths = pgbCharWidths->getCharWidths()->getPointer(0); _fetchCharWidths(m_pScreenFont, pCharWidths); pCharWidths = pgbCharWidths->getCharWidthsLayoutUnits()->getPointer(0); _fetchCharWidths(m_pLayoutFont, pCharWidths); m_pG->setFont(m_pScreenFont); #ifdef BIDI_ENABLED }; #endif } UT_sint32 fp_TextRun::simpleRecalcWidth(UT_sint32 iWidthType, UT_sint32 iLength) #ifndef BIDI_ENABLED const #endif { if(iLength == Calculate_full_width) { iLength = m_iLen; } UT_ASSERT(iLength >= 0); UT_ASSERT((UT_uint32)iLength <= m_iLen); if((UT_uint32)iLength > m_iLen) iLength = (UT_sint32)m_iLen; if (iLength == 0) return 0; UT_GrowBuf * pgbCharWidths; switch(iWidthType) { case Width_type_display: pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); break; case Width_type_layout_units: pgbCharWidths = m_pBL->getCharWidths()->getCharWidthsLayoutUnits(); break; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return 0; break; } UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); UT_sint32 iWidth = 0; { #ifdef BIDI_ENABLED _refreshDrawBuffer(); if(iWidthType == Width_type_display) m_pG->setFont(m_pScreenFont); else m_pG->setFont(m_pLayoutFont); for (UT_sint32 i=0; imeasureString(m_pSpanBuff + i, 0, 1, (UT_uint16*)pCharWidths + m_iOffsetFirst + i); } iWidth += pCharWidths[i + m_iOffsetFirst]; } m_pG->setFont(m_pScreenFont); #else const UT_UCSChar* pSpan; UT_uint32 lenSpan; UT_uint32 offset = m_iOffsetFirst; UT_uint32 len = iLength; bool bContinue = true; while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); if (!bContinue) // if this fails, we are out of sync with the PTbl. { // this probably means we are the right half of a split break; // made to break a paragraph. } UT_ASSERT(lenSpan>0); UT_uint32 iTrueLen; if (lenSpan > len) { iTrueLen = len; } else iTrueLen = lenSpan; for (UT_uint32 i=0; igetCharWidths()->getCharWidths(); UT_GrowBuf *pgbCharWidthsLayout = m_pBL->getCharWidths()->getCharWidthsLayoutUnits(); UT_uint16* pCharWidthsDisplay = pgbCharWidthsDisplay->getPointer(0); UT_uint16* pCharWidthsLayout = pgbCharWidthsLayout->getPointer(0); xxx_UT_DEBUGMSG(("fp_TextRun::recalcWidth: pCharWidthsDisplay 0x%x, pCharWidthsLayout 0x%x\n",pCharWidthsDisplay, pCharWidthsLayout)); m_iWidth = 0; m_iWidthLayoutUnits = 0; FriBidiCharType iVisDirection = getVisDirection(); bool bReverse = (!s_bBidiOS && iVisDirection == FRIBIDI_TYPE_RTL) || (s_bBidiOS && m_iDirOverride == FRIBIDI_TYPE_LTR && m_iDirection == FRIBIDI_TYPE_RTL); UT_sint32 j,k; #if 0 UT_uint32 iResolution = m_pBL->getDocLayout()->getGraphics()->getResolution(); #endif for (UT_uint32 i = 0; i < m_iLen; i++) { // this is a bit tricky, since we want the resulting width array in // logical order, so if we reverse the draw buffer ourselves, we // have to address the draw buffer in reverse j = bReverse ? m_iLen - i - 1 : i; //k = (!bReverse && iVisDirection == FRIBIDI_TYPE_RTL) ? m_iLen - i - 1: i; k = i + m_iOffsetFirst; if(s_bUseContextGlyphs) { m_pG->setFont(m_pLayoutFont); m_pG->measureString(m_pSpanBuff + j, 0, 1, (UT_uint16*)pCharWidthsLayout + k); #if 1 m_pG->setFont(m_pScreenFont); m_pG->measureString(m_pSpanBuff + j, 0, 1, (UT_uint16*)pCharWidthsDisplay + k); #else // this produces much superior width than using measureString; unfortunately // when drawing actual run, we get a better carret location, but out of sync // with the text Windows draws on screen. UT_uint32 iCharWidth, iRem; iCharWidth = *(pCharWidthsLayout + k); iCharWidth *= iResolution; iRem = iCharWidth % UT_LAYOUT_UNITS; iCharWidth /= UT_LAYOUT_UNITS; if(iRem >= UT_LAYOUT_UNITS/2) iCharWidth++; *(pCharWidthsDisplay + k) = iCharWidth; #endif } m_iWidth += pCharWidthsDisplay[k]; m_iWidthLayoutUnits += pCharWidthsLayout[k]; } #else m_iWidth = simpleRecalcWidth(Width_type_display); m_iWidthLayoutUnits = simpleRecalcWidth(Width_type_layout_units); #endif #ifndef BIDI_ENABLED // this is not needed in bidi build, and possible not in non-bidi either #if 0 if(m_iWidth) clearScreen(); #endif #endif xxx_UT_DEBUGMSG(("fp_TextRun::recalcWidth (0x%x, L 0x%x): m_iWidth %d, m_iW*L*U* %d\n",this,m_pLine,m_iWidth, m_iWidthLayoutUnits)); return true; } else { xxx_UT_DEBUGMSG(("fp_TextRun::recalcWidth (0x%x): the run has not changed\n",this)); return false; } #endif } #if 1//def BIDI_ENABLED // this function is just like recalcWidth, except it does not change the character width // information kept by the block, but assumes that information is correct. // the only place it is currently used is the split() function. Since spliting a run into // two does not change the actual character sequence in the block, the width array can stay // untouched bool fp_TextRun::_addupCharWidths(void) { UT_GrowBuf *pgbCharWidthsDisplay = m_pBL->getCharWidths()->getCharWidths(); UT_GrowBuf *pgbCharWidthsLayout = m_pBL->getCharWidths()->getCharWidthsLayoutUnits(); UT_uint16* pCharWidthsDisplay = pgbCharWidthsDisplay->getPointer(0); UT_uint16* pCharWidthsLayout = pgbCharWidthsLayout->getPointer(0); xxx_UT_DEBUGMSG(("fp_TextRun::_addupCharWidths: pCharWidthsDisplay 0x%x, pCharWidthsLayout 0x%x\n",pCharWidthsDisplay, pCharWidthsLayout)); UT_sint32 iWidth = 0; UT_sint32 iWidthLayoutUnits = 0; for (UT_uint32 i = m_iOffsetFirst; i < m_iLen + m_iOffsetFirst; i++) { iWidth += pCharWidthsDisplay[i]; iWidthLayoutUnits += pCharWidthsLayout[i]; } if(iWidth != m_iWidth) { m_iWidth = iWidth; m_iWidthLayoutUnits = iWidthLayoutUnits; return true; } return false; } #endif void fp_TextRun::_clearScreen(bool /* bFullLineHeightRect */) { UT_ASSERT(!m_bDirty); UT_ASSERT(m_pG->queryProperties(GR_Graphics::DGP_SCREEN)); #ifdef BIDI_ENABLED if(!m_pLine->isEmpty() && m_pLine->getLastVisRun() == this) //#TF must be last visual run #else if(!m_pLine->isEmpty() && m_pLine->getLastTextRun() == this) #endif { // Last run on the line so clear to end. m_pLine->clearScreenFromRunToEnd(this); } else { m_pG->setFont(m_pScreenFont); /* TODO this should not be hard-coded. We need to figure out what the appropriate background color for this run is, and use that. Note that it could vary on a run-by-run basis, since document facilities allow the background color to be changed, for things such as table cells. */ // we need to use here page color, not highlight color, otherwise // we endup with higlighted margin //UT_RGBColor clrNormalBackground(m_colorHL.m_red, m_colorHL.m_grn, m_colorHL.m_blu); UT_RGBColor clrNormalBackground(m_colorPG.m_red, m_colorPG.m_grn, m_colorPG.m_blu); if (m_pField) { UT_setColor (clrNormalBackground, 220, 220, 220); } m_pG->setColor(clrNormalBackground); UT_sint32 xoff = 0, yoff = 0; m_pLine->getScreenOffsets(this, xoff, yoff); // // Handle case where character extend behind the left side // like italic Times New Roman f // fp_Line * thisLine = getLine(); fp_Run * pPrev = getPrev(); UT_sint32 leftClear = 0; if(thisLine != NULL) { while(pPrev != NULL && pPrev->getLine() == thisLine && pPrev->getLength()== 0) { pPrev = pPrev->getPrev(); } leftClear = getDescent(); bool bthis = (pPrev != NULL) && (pPrev->getLine() ==thisLine); if(bthis && pPrev->getType() == FPRUN_TEXT) leftClear = 0; if(bthis && pPrev->getType() == FPRUN_FIELD) leftClear = 0; if(bthis && pPrev->getType() == FPRUN_IMAGE) leftClear = 0; } m_pG->fillRect(clrNormalBackground,xoff-leftClear , yoff, m_iWidth+leftClear, m_pLine->getHeight()); } } void fp_TextRun::_draw(dg_DrawArgs* pDA) { /* Upon entry to this function, pDA->yoff is the BASELINE of this run, NOT the top. */ #ifndef NDEBUG FV_View* ppView = m_pBL->getDocLayout()->getView(); if(ppView) UT_ASSERT(ppView && ppView->isCursorOn()==false); #endif #ifdef BIDI_ENABLED _refreshDrawBuffer(); xxx_UT_DEBUGMSG(("fp_TextRun::_draw (0x%x): m_iVisDirection %d, m_iDirection %d\n", this, m_iVisDirection, m_iDirection)); #endif UT_sint32 yTopOfRun = pDA->yoff - m_iAscent-1; // Hack to remove UT_sint32 yTopOfSel = yTopOfRun+1; // final character dirt /* TODO We should add more possibilities for text placement here. It shouldn't be too hard. Just adjust the math a little. See bug 1297 */ if (m_fPosition == TEXT_POSITION_SUPERSCRIPT) { yTopOfRun -= m_iAscent * 1/2; } else if (m_fPosition == TEXT_POSITION_SUBSCRIPT) { yTopOfRun += m_iDescent /* * 3/2 */; } UT_ASSERT(pDA->pG == m_pG); const UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); UT_uint32 iBase = m_pBL->getPosition(); if(!m_pHyperlink || !m_pG->queryProperties(GR_Graphics::DGP_SCREEN)) m_pG->setColor(m_colorFG); /* TODO this should not be hard-coded. We should calculate an appropriate selection background color based on the color of the foreground text, probably. */ UT_RGBColor clrNormalBackground(m_colorHL.m_red, m_colorHL.m_grn, m_colorHL.m_blu); UT_RGBColor clrSelBackground(192, 192, 192); if (m_pField) { UT_setColor(clrNormalBackground,220, 220, 220); UT_setColor(clrSelBackground,112, 112, 112); } // test the nearness of our selection colour to text, highlight and page // colours if(clrSelBackground %= m_colorPG) clrSelBackground += 20; if(clrSelBackground %= m_colorHL) clrSelBackground += 20; if(clrSelBackground %= m_colorFG) clrSelBackground += 20; /* the old way of doing things was very inefficient; for each chunk of this run that had a different background we first drew the background rectangle, then drew the text over it, and then moved onto the next chunk. This is very involved since to draw the text in pieces we have to calculate the screen offset of each chunk using character widths. This is is what we will do instead: (1) draw the background using the background colour for the whole run in a single go (2) draw any selection background where needed over the basic background (3) draw the whole text in a single go over the composite background */ // draw the background for the whole run, but only in sreen context or if // it is not white UT_RGBColor clrWhite(255,255,255); bool bDrawBckg = (m_pG->queryProperties(GR_Graphics::DGP_SCREEN) || clrNormalBackground != clrWhite); if(bDrawBckg) { m_pG->fillRect( clrNormalBackground, pDA->xoff, yTopOfSel + m_iAscent - m_pLine->getAscent(), m_iWidth, m_pLine->getHeight()); } UT_uint32 iRunBase = iBase + m_iOffsetFirst; FV_View* pView = m_pBL->getDocLayout()->getView(); UT_uint32 iSelAnchor = pView->getSelectionAnchor(); UT_uint32 iPoint = pView->getPoint(); UT_uint32 iSel1 = UT_MIN(iSelAnchor, iPoint); UT_uint32 iSel2 = UT_MAX(iSelAnchor, iPoint); UT_ASSERT(iSel1 <= iSel2); if (pView->getFocus()!=AV_FOCUS_NONE && iSel1 != iSel2) { if (iSel1 <= iRunBase) { if (iSel2 > iRunBase) { if (iSel2 >= (iRunBase + m_iLen)) { // the whole run is selected _fillRect(clrSelBackground, pDA->xoff, yTopOfSel, m_iOffsetFirst, m_iLen, pgbCharWidths); } else { // the first part is selected, the second part is not _fillRect(clrSelBackground, pDA->xoff, yTopOfSel, m_iOffsetFirst, iSel2 - iRunBase, pgbCharWidths); } } } else if (iSel1 < (iRunBase + m_iLen)) { if (iSel2 >= (iRunBase + m_iLen)) { // the second part is selected _fillRect(clrSelBackground, pDA->xoff, yTopOfSel, iSel1 - iBase, m_iLen - (iSel1 - iRunBase), pgbCharWidths); } else { // a midle section is selected _fillRect(clrSelBackground, pDA->xoff, yTopOfSel, iSel1 - iBase, iSel2 - iSel1, pgbCharWidths); } } } /* if the text on either side of this run is in italics, there is a good chance that the background covered some of the text; to fix this we call _drawLastChar and _drawFirstChar for those runs, but we will not do so undiscriminately -- we need to do this only if the prev or next runs are overhanging (i.e., italicized) and if we are in a screen-like context (screen-like context is one in which drawing a white rectangle over a piece of text results in the text being erased; apart from the sreen, a Windows printer can behave like this). */ if(bDrawBckg && m_pG->queryProperties(GR_Graphics::DGP_OPAQUEOVERLAY)) { #ifdef BIDI_ENABLED fp_Run * pNext = getNextVisual(); fp_Run * pPrev = getPrevVisual(); #else fp_Run * pNext = m_pNext && m_pNext->getLine() == m_pLine ? m_pNext : NULL; fp_Run * pPrev = m_pPrev && m_pPrev->getLine() == m_pLine ? m_pPrev : NULL; #endif if(pNext && pNext->getType() == FPRUN_TEXT) { fp_TextRun * pT = static_cast(pNext); UT_sint32 ytemp = pDA->yoff+(pT->getY()-getY())-pT->m_iAscent-1; if (pT->m_fPosition == TEXT_POSITION_SUPERSCRIPT) { ytemp -= pT->m_iAscent * 1/2; } else if (pT->m_fPosition == TEXT_POSITION_SUBSCRIPT) { ytemp += pT->m_iDescent /* * 3/2 */; } if(pT->m_bIsOverhanging) pT->_drawFirstChar(pDA->xoff + m_iWidth,ytemp); } if(pPrev && pPrev->getType() == FPRUN_TEXT) { fp_TextRun * pT = static_cast(pPrev); UT_sint32 ytemp = pDA->yoff+(pT->getY()-getY())-pT->m_iAscent-1; if (pT->m_fPosition == TEXT_POSITION_SUPERSCRIPT) { ytemp -= pT->m_iAscent * 1/2; } else if (pT->m_fPosition == TEXT_POSITION_SUBSCRIPT) { ytemp += pT->m_iDescent /* * 3/2 */; } if(pT->m_bIsOverhanging) pT->_drawLastChar(pDA->xoff,ytemp, pgbCharWidths); } } // now draw the whole string m_pG->setFont(m_pScreenFont); m_pG->setColor(m_colorFG); // set colour just in case we drew a first/last char with a diff colour #ifdef BIDI_ENABLED // since we have the visual string in the draw buffer, we just call m_pGr->drawChars() m_pG->drawChars(m_pSpanBuff, 0, m_iLen, pDA->xoff, yTopOfRun); #else _drawPart(pDA->xoff, yTopOfRun, m_iOffsetFirst, m_iLen, pgbCharWidths); #endif drawDecors(pDA->xoff, yTopOfRun); if(pView->getShowPara()) { _drawInvisibles(pDA->xoff, yTopOfRun); } // TODO: draw this underneath (ie, before) the text and decorations m_bSquiggled = false; m_pBL->findSquigglesForRun(this); } void fp_TextRun::_fillRect(UT_RGBColor& clr, UT_sint32 xoff, UT_sint32 yoff, UT_uint32 iPos1, UT_uint32 iLen, const UT_GrowBuf* pgbCharWidths) { /* Upon entry to this function, yoff is the TOP of the run, NOT the baseline. */ #ifndef NDEBUG FV_View* ppView = m_pBL->getDocLayout()->getView(); if(ppView) UT_ASSERT(ppView && ppView->isCursorOn()==false); #endif // we also need to support this in printing // NO! we do not and must not -- this is only used to draw selections // and field background and we do not want to see these printed if (m_pG->queryProperties(GR_Graphics::DGP_SCREEN)) { UT_Rect r; _getPartRect(&r, xoff, yoff, iPos1, iLen, pgbCharWidths); r.height = m_pLine->getHeight(); r.top = r.top + m_iAscent - m_pLine->getAscent(); m_pG->fillRect(clr, r.left, r.top, r.width, r.height); } } void fp_TextRun::_getPartRect(UT_Rect* pRect, UT_sint32 xoff, UT_sint32 yoff, UT_uint32 iStart, UT_uint32 iLen, const UT_GrowBuf * pgbCharWidths) { /* Upon entry to this function, yoff is the TOP of the run, NOT the baseline. */ #ifndef BIDI_ENABLED pRect->left = xoff; #endif pRect->top = yoff; pRect->height = m_iHeight; pRect->width = 0; // that's enough for zero-length run if (m_iLen == 0) { #ifdef BIDI_ENABLED pRect->left = xoff; #endif return; } //Should this be an error condition? I treat it as a zero length run, but other might not const UT_uint16 * pCharWidths = pgbCharWidths->getPointer(0); #ifdef BIDI_ENABLED pRect->left = 0;//#TF need 0 because of BiDi, need to calculate the width of the non-selected //section first rather than the abs pos of the left corner #endif UT_ASSERT(pCharWidths); if (!pCharWidths) { UT_DEBUGMSG(("TODO: Investigate why pCharWidths is NULL?")); return; } UT_uint32 i; if (iStart > m_iOffsetFirst) { for (i=m_iOffsetFirst; ileft += pCharWidths[i]; } } #ifdef BIDI_ENABLED if(getVisDirection() == FRIBIDI_TYPE_LTR) { pRect->left += xoff; //if this is ltr then adding xoff is all that is needed } #endif for (i=iStart; i<(iStart + iLen); i++) { pRect->width += pCharWidths[i]; } #ifdef BIDI_ENABLED //in case of rtl we are now in the position to calculate the position of the the left corner if(getVisDirection() == FRIBIDI_TYPE_RTL) pRect->left = xoff + m_iWidth - pRect->left - pRect->width; #endif } #ifdef BIDI_ENABLED inline UT_UCSChar fp_TextRun::_getContextGlyph(const UT_UCSChar * pSpan, UT_uint32 len, UT_uint32 offset, UT_UCSChar *prev, UT_UCSChar *after) const { UT_sint32 iStop2 = (UT_sint32) CONTEXT_BUFF_SIZE < (UT_sint32)(len - 1) ? CONTEXT_BUFF_SIZE : len - 1; UT_contextGlyph cg; UT_UCSChar next[CONTEXT_BUFF_SIZE + 1]; xxx_UT_DEBUGMSG(("fp_TextRun::_getContextGlyph: len %d, offset %d\n", len,offset)); // now for each character in this fragment we create // an array of the chars that follow it, get the char // that precedes it and then translate it using our // UT_contextGlyph class, and we are done UT_sint32 i; for(i=0; i < iStop2; i++) next[i] = pSpan[i+offset+1]; for(UT_sint32 j = 0; after[j]!=0 && (UT_uint32)i < CONTEXT_BUFF_SIZE; i++,j++) next[i] = after[j]; next[i] = 0; switch(offset) { case 0: break; case 1: prev[1] = prev[0]; prev[0] = pSpan[0]; break; default: prev[1] = pSpan[offset-2]; prev[0] = pSpan[offset-1]; } return cg.getGlyph(&pSpan[offset],&prev[0],&next[0]); } inline void fp_TextRun::_getContext(const UT_UCSChar *pSpan, UT_uint32 lenSpan, UT_uint32 len, UT_uint32 offset, UT_UCSChar * prev, UT_UCSChar * after) const { // first the char preceding this part of the run, which is simple // NB. cannot call getSpanPtr with negative offset, or we will get // into the undo buffer const UT_UCSChar * pPrev = 0, * pNext = 0; UT_uint32 lenPrev, lenAfter; prev[0] = 0; prev[1] = 0; prev[2] = 0; if(m_iOffsetFirst > 1) { if(m_pBL->getSpanPtr(offset - 2, &pPrev, &lenPrev)) { prev[1] = *pPrev; if(lenPrev > 1) prev[0] = *(pPrev+1); else if(m_pBL->getSpanPtr(offset - 1, &pPrev, &lenPrev)) prev[0] = *(pPrev+1); } else if(m_pBL->getSpanPtr(offset - 1, &pPrev, &lenPrev)) { prev[0] = *pPrev; } } else if(m_iOffsetFirst > 0) { if(m_pBL->getSpanPtr(offset - 1, &pPrev, &lenPrev)) prev[0] = *pPrev; } lenPrev = 2; xxx_UT_DEBUGMSG(("fp_TextRun::_getContext: prev[0] %d, prev[1] %d\n" " m_iOffsetFirst %d, offset %d\n", prev[0],prev[1],m_iOffsetFirst,offset)); // how many characters at most can we retrieve? UT_sint32 iStop = (UT_sint32) CONTEXT_BUFF_SIZE < (UT_sint32)(lenSpan - len) ? CONTEXT_BUFF_SIZE : lenSpan - len; UT_sint32 i; // first, getting anything that might be in the span buffer for(i=0; i< iStop;i++) after[i] = pSpan[len+i]; // for anything that we miss, we need to get it the hard way // as it is located in different spans while(i < CONTEXT_BUFF_SIZE && m_pBL->getSpanPtr(offset + len + i, &pNext, &lenAfter)) { for(UT_uint32 j = 0; j < lenAfter && (UT_uint32)i < CONTEXT_BUFF_SIZE; j++,i++) after[i] = pNext[j]; } // now we have our trailing chars, so we null-terminate the array after[i] = 0; } #endif #ifdef BIDI_ENABLED void fp_TextRun::_refreshDrawBuffer() { if(m_bRefreshDrawBuffer) { m_bRefreshDrawBuffer = false; FriBidiCharType iVisDir = getVisDirection(); const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 offset = 0; UT_uint32 len = m_iLen; bool bContinue = true; UT_contextGlyph cg; if(m_iLen > m_iSpanBuffSize) //the buffer too small, reallocate { delete[] m_pSpanBuff; m_pSpanBuff = new UT_UCSChar[m_iLen + 1]; m_iSpanBuffSize = m_iLen; UT_ASSERT(m_pSpanBuff); } for(;;) // retrive the span and do any necessary processing { bContinue = m_pBL->getSpanPtr(offset + m_iOffsetFirst, &pSpan, &lenSpan); //this sometimes happens with fields ... if(!bContinue) break; UT_uint32 iTrueLen = (lenSpan > len) ? len : lenSpan; if(m_iDirection != FRIBIDI_TYPE_ON || iVisDir != FRIBIDI_TYPE_RTL) { /* here we need to handle context glyph shapes. For that we will use our class UT_contextGlyph, for which we need to retrieve one character before the one in question and an unspecified number of chars to follow. */ if(s_bUseContextGlyphs) { // now we will retrieve 5 characters that follow this part of the run // and two chars that precede it // three small buffers, one to keep the chars past this part // and one that we will use to create the "trailing" chars // for each character in this fragment UT_UCSChar next[CONTEXT_BUFF_SIZE + 1]; UT_UCSChar prev[CONTEXT_BUFF_SIZE + 1]; // NB: _getContext requires block offset _getContext(pSpan,lenSpan,len,offset+m_iOffsetFirst,&prev[0],&next[0]); cg.renderString(pSpan, &m_pSpanBuff[offset],iTrueLen,&prev[0],&next[0]); } else //do not use context glyphs { UT_UCS_strncpy(m_pSpanBuff + offset, pSpan, iTrueLen); } } else { //this is 'other neutral' visually rtl span, we need to deal //with mirror characters for (UT_uint32 i = 0; i < iTrueLen; i++) m_pSpanBuff[offset + i] = getMirrorChar(pSpan[i]); } if(iTrueLen == len) { break; } offset += iTrueLen; len -= iTrueLen; } // for(;;) // if we are on a non-bidi OS, we have to reverse any RTL runs // if we are on bidi OS, we have to reverse RTL runs that have direction // override set to LTR, to preempty to OS reversal of such text if((!s_bBidiOS && iVisDir == FRIBIDI_TYPE_RTL) || (s_bBidiOS && m_iDirOverride == FRIBIDI_TYPE_LTR && m_iDirection == FRIBIDI_TYPE_RTL)) UT_UCS_strnrev(m_pSpanBuff, m_iLen); } //if(m_bRefreshDrawBuffer) } #endif /* xoff is the right edge of this run !!! */ void fp_TextRun::_drawLastChar(UT_sint32 xoff, UT_sint32 yoff,const UT_GrowBuf * pgbCharWidths) { if(!m_iLen) return; // have to sent font (and colour!), since we were called from a run using different font m_pG->setFont(m_pScreenFont); m_pG->setColor(m_colorFG); #ifdef BIDI_ENABLED FriBidiCharType iVisDirection = getVisDirection(); if(!s_bBidiOS) { // m_pSpanBuff is in visual order, so we just draw the last char UT_uint32 iPos = iVisDirection == FRIBIDI_TYPE_LTR ? m_iLen - 1 : 0; m_pG->drawChars(m_pSpanBuff, m_iLen - 1, 1, xoff - *(pgbCharWidths->getPointer(m_iOffsetFirst + iPos)), yoff); } else { UT_uint32 iPos = iVisDirection == FRIBIDI_TYPE_LTR ? m_iLen - 1 : 0; m_pG->drawChars(m_pSpanBuff, iPos, 1, xoff - *(pgbCharWidths->getPointer(m_iOffsetFirst + iPos)), yoff); } #else const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; bool bSpan; bSpan = m_pBL->getSpanPtr(m_iOffsetFirst + m_iLen - 1, &pSpan, &lenSpan); UT_ASSERT(bSpan); m_pG->drawChars(pSpan, 0, 1, xoff - *(pgbCharWidths->getPointer(m_iOffsetFirst + m_iLen -1)), yoff); #endif } void fp_TextRun::_drawFirstChar(UT_sint32 xoff, UT_sint32 yoff) { if(!m_iLen) return; // have to sent font (and colour!), since we were called from a run using different font m_pG->setFont(m_pScreenFont); m_pG->setColor(m_colorFG); #ifdef BIDI_ENABLED if(!s_bBidiOS) { // m_pSpanBuff is in visual order, so we just draw the last char m_pG->drawChars(m_pSpanBuff, 0, 1, xoff, yoff); } else { UT_uint32 iPos = getVisDirection() == FRIBIDI_TYPE_RTL ? m_iLen - 1 : 0; m_pG->drawChars(m_pSpanBuff, iPos, 1, xoff, yoff); } #else const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; bool bSpan; bSpan = m_pBL->getSpanPtr(m_iOffsetFirst, &pSpan, &lenSpan); UT_ASSERT(bSpan); m_pG->drawChars(pSpan, 0, 1, xoff, yoff); #endif } void fp_TextRun::_drawPart(UT_sint32 xoff, UT_sint32 yoff, UT_uint32 iStart, UT_uint32 iLen, const UT_GrowBuf * pgbCharWidths) { /* Upon entry to this function, yoff is the TOP of the run, NOT the baseline. */ #ifndef BIDI_ENABLED const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 offset = iStart; UT_uint32 len = iLen; bool bContinue = true; #endif // don't even try to draw a zero-length run if (m_iLen == 0) { return; } UT_ASSERT(iStart >= m_iOffsetFirst); UT_ASSERT(iStart + iLen <= m_iOffsetFirst + m_iLen); UT_uint32 iLeftWidth = 0; const UT_uint16 * pCharWidths = pgbCharWidths->getPointer(0); UT_ASSERT(pCharWidths); if (!pCharWidths) { UT_DEBUGMSG(("TODO: Investigate why pCharWidths is NULL?")); return; } UT_uint32 i; for (i=m_iOffsetFirst; idrawChars(m_pSpanBuff + (m_iLen + m_iOffsetFirst - iStart) - iLen, 0, iLen, xoff + m_iWidth - iLeftWidth, yoff); // if the buffer is not reversed because this is simple LTR run, then // iLeftWidth is precisely that else if(iVisDirection == FRIBIDI_TYPE_LTR) m_pG->drawChars(m_pSpanBuff + iStart - m_iOffsetFirst, 0, iLen, xoff + iLeftWidth, yoff); else // if the buffer is not reversed because the OS will reverse it, then we // draw from it as if it was LTR buffer, but iLeftWidth is right width m_pG->drawChars(m_pSpanBuff + iStart - m_iOffsetFirst, 0, iLen, xoff + m_iWidth - iLeftWidth, yoff); #else while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); //if(!bContinue) // break; //no span found UT_ASSERT(lenSpan>0); if (len <= lenSpan) { m_pG->drawChars(pSpan, 0, len, xoff + iLeftWidth, yoff); bContinue = false; } else { m_pG->drawChars(pSpan, 0, lenSpan, xoff + iLeftWidth, yoff); for(UT_uint32 j = 0; j < lenSpan; j++) { iLeftWidth += pCharWidths[offset + j]; } offset += lenSpan; len -= lenSpan; UT_ASSERT(offset >= m_iOffsetFirst); UT_ASSERT(offset + len <= m_iOffsetFirst + m_iLen); } } #endif } void fp_TextRun::_drawInvisibleSpaces(UT_sint32 xoff, UT_sint32 yoff) { UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 len = m_iLen; UT_sint32 iWidth = 0; UT_sint32 cur_linewidth = 1+ (UT_MAX(10,m_iAscent)-10)/8; UT_sint32 iRectSize = cur_linewidth * 3 / 2; bool bContinue = true; UT_uint32 offset = m_iOffsetFirst; UT_RGBColor clrShowPara(127,127,127); #ifndef NDEBUG FV_View* ppView = m_pBL->getDocLayout()->getView(); if(ppView) UT_ASSERT(ppView && ppView->isCursorOn()==false); #endif //UT_DEBUGMSG(("---------\n")); if(findCharacter(0, UCS_SPACE) > 0){ while(bContinue){ bContinue = m_pBL->getSpanPtr(offset,&pSpan,&lenSpan); //if(!bContinue) // break; //no span found #ifdef DEBUG if(lenSpan <= 0) { const UT_UCSChar* mypSpan = NULL; unsigned char buff[500]; UT_uint32 mylenSpan = 0; m_pBL->getSpanPtr(m_iOffsetFirst,&mypSpan,&mylenSpan); for(UT_uint32 i = 0; i < mylenSpan; i++) buff[i] = (unsigned char) mypSpan[i]; UT_DEBUGMSG(("fp_TextRun::_drawInvisibleSpaces (0x%x):\n" " m_iOffsetFirst %d, m_iLen %d\n" " mylenSpan %d, span: %s\n" " lenSpan %d, len %d, offset %d\n", this,m_iOffsetFirst,m_iLen,mylenSpan,buff,lenSpan, len, offset)); } #endif //UT_ASSERT(lenSpan > 0); if(lenSpan > len){ lenSpan = len; } UT_uint32 iy = yoff + getAscent() * 2 / 3; for (UT_uint32 i = 0;i < lenSpan;i++){ if(pSpan[i] == UCS_SPACE){ m_pG->fillRect(clrShowPara,xoff + iWidth + (pCharWidths[i + offset] - iRectSize) / 2,iy,iRectSize,iRectSize); } iWidth += pCharWidths[i + offset]; } if (len <= lenSpan){ bContinue = false; }else{ offset += lenSpan; len -= lenSpan; } } } } void fp_TextRun::_drawInvisibles(UT_sint32 xoff, UT_sint32 yoff) { #ifndef NDEBUG FV_View* ppView = m_pBL->getDocLayout()->getView(); if(ppView) UT_ASSERT(ppView && ppView->isCursorOn()==false); #endif if (!(m_pG->queryProperties(GR_Graphics::DGP_SCREEN))){ return; } _drawInvisibleSpaces(xoff,yoff); } void fp_TextRun::_drawSquiggle(UT_sint32 top, UT_sint32 left, UT_sint32 right) { if (!(m_pG->queryProperties(GR_Graphics::DGP_SCREEN))) { return; } m_bSquiggled = true; UT_sint32 nPoints = (right - left + 3)/2; UT_ASSERT(nPoints >= 1); //can be 1 for overstriking chars /* NB: This array gets recopied inside the polyLine implementation to move the coordinates into a platform-specific point structure. They're all x, y but different widths. Bummer. */ UT_Point * points, scratchpoints[100]; if ((unsigned)nPoints < (sizeof(scratchpoints)/sizeof(scratchpoints[0]))) { points = scratchpoints; } else { points = new UT_Point[nPoints]; } UT_ASSERT(points); points[0].x = left; points[0].y = top; bool bTop = false; for (UT_sint32 i = 1; i < nPoints; i++, bTop = !bTop) { points[i].x = points[i-1].x + 2; points[i].y = (bTop ? top : top + 2); } if (points[nPoints-1].x > right) { points[nPoints-1].x = right; points[nPoints-1].y = top + 1; } m_pG->polyLine(points, nPoints); if (points != scratchpoints) delete points; } void fp_TextRun::drawSquiggle(UT_uint32 iOffset, UT_uint32 iLen) { // UT_ASSERT(iLen > 0); if (iLen == 0) { // I think this is safe, although it begs the question, why did we get called if iLen is zero? TODO return; } UT_sint32 xoff = 0, yoff = 0; UT_sint32 iAscent = m_pLine->getAscent(); UT_sint32 iDescent = m_pLine->getDescent(); // we'd prefer squiggle to leave one pixel below the baseline, // but we need to force all three pixels inside the descent UT_sint32 iGap = (iDescent > 3) ? 1 : (iDescent - 3); UT_RGBColor clrSquiggle(255, 0, 0); m_pG->setColor(clrSquiggle); m_pLine->getScreenOffsets(this, xoff, yoff); UT_Rect r; const UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); _getPartRect( &r, xoff, yoff, iOffset, iLen, pgbCharWidths); _drawSquiggle(r.top + iAscent + iGap, r.left, r.left + r.width); } UT_sint32 fp_TextRun::findCharacter(UT_uint32 startPosition, UT_UCSChar Character) const { // NOTE: startPosition is run-relative // NOTE: return value is block-relative (don't ask me why) const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 offset = m_iOffsetFirst + startPosition; UT_uint32 len = m_iLen - startPosition; bool bContinue = true; if ((m_iLen > 0) && (startPosition < m_iLen)) { UT_uint32 i; while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); //if(!bContinue) // break; //UT_ASSERT(lenSpan>0); if (len <= lenSpan) { for(i = 0; i < len; i++) { if (pSpan[i] == Character) return offset + i; } bContinue = false; } else { for(i = 0; i < lenSpan; i++) { if (pSpan[i] == Character) return offset + i; } offset += lenSpan; len -= lenSpan; UT_ASSERT(offset >= m_iOffsetFirst); UT_ASSERT(offset + len <= m_iOffsetFirst + m_iLen); } } } // not found return -1; } bool fp_TextRun::getCharacter(UT_uint32 run_offset, UT_UCSChar &Character) const { const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_ASSERT(run_offset < m_iLen); if (m_iLen > 0) { if (m_pBL->getSpanPtr(run_offset + m_iOffsetFirst, &pSpan, &lenSpan)) { UT_ASSERT(lenSpan>0); Character = pSpan[0]; return true; } } // not found return false; } bool fp_TextRun::isLastCharacter(UT_UCSChar Character) const { UT_UCSChar c; if (getCharacter(m_iLen - 1, c)) return c == Character; // not found return false; } bool fp_TextRun::isFirstCharacter(UT_UCSChar Character) const { UT_UCSChar c; if (getCharacter(0, c)) return c == Character; // not found return false; } bool fp_TextRun::doesContainNonBlankData(void) const { if(m_iLen > 0) { const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 offset = m_iOffsetFirst; UT_uint32 len = m_iLen; bool bContinue = true; UT_uint32 i; while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); //if(!bContinue) // break; //no span found if(lenSpan <= 0) { fp_Line * pLine = m_pBL->getFirstLine(); while(pLine) { pLine = pLine->getNext(); } } //UT_ASSERT(lenSpan>0); if (len <= lenSpan) { for(i = 0; i < len; i++) { if (pSpan[i] != UCS_SPACE) return true; } bContinue = false; } else { for(i = 0; i < lenSpan; i++) { if (pSpan[i] != UCS_SPACE) return true; } offset += lenSpan; len -= lenSpan; UT_ASSERT(offset >= m_iOffsetFirst); UT_ASSERT(offset + len <= m_iOffsetFirst + m_iLen); } } } // Only spaces found; return false; } inline bool fp_TextRun::isSuperscript(void) const { return (m_fPosition == TEXT_POSITION_SUPERSCRIPT); } inline bool fp_TextRun::isSubscript(void) const { return (m_fPosition == TEXT_POSITION_SUBSCRIPT); } UT_sint32 fp_TextRun::findTrailingSpaceDistance(void) const { UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); UT_sint32 iTrailingDistance = 0; if(m_iLen > 0) { UT_UCSChar c; UT_sint32 i; for (i = m_iLen - 1; i >= 0; i--) { if(getCharacter(i, c) && (UCS_SPACE == c)) { iTrailingDistance += pCharWidths[i + m_iOffsetFirst]; } else { break; } } } return iTrailingDistance; } UT_sint32 fp_TextRun::findTrailingSpaceDistanceInLayoutUnits(void) const { UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidthsLayoutUnits(); UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); UT_sint32 iTrailingDistance = 0; if(m_iLen > 0) { UT_UCSChar c; UT_sint32 i; for (i = m_iLen - 1; i >= 0; i--) { if(getCharacter(i, c) && (UCS_SPACE == c)) { iTrailingDistance += pCharWidths[i + m_iOffsetFirst]; } else { break; } } } return iTrailingDistance; } void fp_TextRun::resetJustification() { if(m_iSpaceWidthBeforeJustification != JUSTIFICATION_NOT_USED && m_iSpaceWidthBeforeJustification != JUSTIFICATION_FAKE) { UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); UT_sint32 i = findCharacter(0, UCS_SPACE); while (i >= 0) { if(pCharWidths[i] != m_iSpaceWidthBeforeJustification) { // set width of spaces back to normal. m_iWidth -= pCharWidths[i] - m_iSpaceWidthBeforeJustification; pCharWidths[i] = m_iSpaceWidthBeforeJustification; } // keep looping i = findCharacter(i+1-m_iOffsetFirst, UCS_SPACE); } m_bRecalcWidth = true; } m_iSpaceWidthBeforeJustification = JUSTIFICATION_NOT_USED; } void fp_TextRun::distributeJustificationAmongstSpaces(UT_sint32 iAmount, UT_uint32 iSpacesInRun) { UT_GrowBuf * pgbCharWidths = m_pBL->getCharWidths()->getCharWidths(); UT_uint16* pCharWidths = pgbCharWidths->getPointer(0); if(!iAmount || !iSpacesInRun) { // we still want to set the m_iSpaceWidthBeforeJustification member, to prevent such runs // from being merged; we will use JUSTIFICATION_FAKE to indicated that no change was made, // but this run is still considered justified m_iSpaceWidthBeforeJustification = JUSTIFICATION_FAKE; return; } if(iSpacesInRun && m_iLen > 0) { m_iWidth += iAmount; UT_sint32 i = findCharacter(0, UCS_SPACE); while ((i >= 0) && (iSpacesInRun)) { // remember how wide spaces in this run "really" were if(m_iSpaceWidthBeforeJustification == JUSTIFICATION_NOT_USED) m_iSpaceWidthBeforeJustification = pCharWidths[i]; UT_sint32 iThisAmount = iAmount / iSpacesInRun; pCharWidths[i] += iThisAmount; iAmount -= iThisAmount; iSpacesInRun--; // keep looping i = findCharacter(i+1-m_iOffsetFirst, UCS_SPACE); } m_bRecalcWidth = true; } UT_ASSERT(iAmount == 0); } UT_uint32 fp_TextRun::countTrailingSpaces(void) const { UT_uint32 iCount = 0; if(m_iLen > 0) { UT_UCSChar c; UT_sint32 i; for (i = m_iLen - 1; i >= 0; i--) { if(getCharacter(i, c) && (UCS_SPACE == c)) { iCount++; } else { break; } } } return iCount; } /* if this run contains only spaces, we will return the count as a negative value, this will save us having to call doesContainNonBlankData() in a couple of loops in fp_Line */ UT_sint32 fp_TextRun::countJustificationPoints(void) const { UT_sint32 iCount = 0; bool bNonBlank = false; UT_sint32 iOldI; if(m_iLen > 0) { UT_sint32 i = findCharacter(0, UCS_SPACE); iOldI = m_iOffsetFirst - 1; while (i >= 0) { if(iOldI < i-1) // i.e., something between the two spaces bNonBlank = true; iCount++; iOldI = i; // keep looping i = findCharacter(i+1-m_iOffsetFirst, UCS_SPACE); } } UT_ASSERT(iCount >= 0); if(!bNonBlank) { return -iCount; } else { return iCount; } } bool fp_TextRun::canContainPoint(void) const { if (m_pField) { return false; } else { return true; } } #ifdef BIDI_ENABLED //fill str of size iMax with the text of the run, return 0 on success, //size of str required if not not enough space, -1 otherwise //it further returns the len copied (or desired to be copied) into pStr in iMax UT_sint32 fp_TextRun::getStr(UT_UCSChar * pStr, UT_uint32 &iMax) { const UT_UCSChar* pSpan = NULL; UT_uint32 lenSpan = 0; UT_uint32 offset = m_iOffsetFirst; UT_uint32 len = m_iLen; bool bContinue = true; UT_UCSChar * pStrPos = pStr; if(iMax <= len) { iMax = len; return(len);//if there is not enough space in the passed string //return size needed } if (len > 0) { while (bContinue) { bContinue = m_pBL->getSpanPtr(offset, &pSpan, &lenSpan); UT_ASSERT(lenSpan>0); if (len <= lenSpan) //copy the entire len to pStr and return 0 { UT_ASSERT(len>0); UT_UCS_strncpy(pStrPos, pSpan, len); pStr[len] = 0; //make sure the string is 00-terminated iMax = m_iLen; return(false); } else //copy what we have got and move on to the next span { UT_UCS_strncpy(pStrPos, pSpan, lenSpan); offset += lenSpan; len -= lenSpan; pStrPos += lenSpan; } } } else //this run is empty, fill pStr with 00 and return 0 { *pStr = 0; iMax = 0; return false; } UT_ASSERT(UT_SHOULD_NOT_HAPPEN); return false; } void fp_TextRun::setDirection(FriBidiCharType dir, FriBidiCharType dirOverride) { if( !m_iLen || ( dir == FRIBIDI_TYPE_UNSET && m_iDirection != FRIBIDI_TYPE_UNSET && dirOverride == m_iDirOverride ) ) return; //ignore 0-length runs, let them be treated on basis of the app defaults FriBidiCharType prevDir = m_iDirOverride == FRIBIDI_TYPE_UNSET ? m_iDirection : m_iDirOverride; if(dir == FRIBIDI_TYPE_UNSET) { // only do this once if(m_iDirection == FRIBIDI_TYPE_UNSET) { UT_UCSChar firstChar; getCharacter(0, firstChar); m_iDirection = fribidi_get_type((FriBidiChar)firstChar); } } else //meaningfull value received { m_iDirection = dir; } xxx_UT_DEBUGMSG(("fp_TextRun (0x%x)::setDirection: %d (passed %d, override %d, prev. %d)\n", this, m_iDirection, dir, m_iDirOverride, prevDir)); FriBidiCharType iOldOverride = m_iDirOverride; m_iDirOverride = dirOverride; // if we set dir override to a strong value, set also visual direction // if we set it to UNSET, and the new direction is srong, then we set // it to that direction, if it is weak, we have to make the line // to calculate it if(dirOverride != FRIBIDI_TYPE_UNSET) setVisDirection(dirOverride); /* if this run belongs to a line we have to notify the line that that it now contains a run of this direction, if it does not belong to a line this will be taken care of by the fp_Line:: member function used to add the run to the line (generally, we set it here if this is a run that is being typed in and it gets set in the member functions when the run is loaded from a document on the disk.) */ FriBidiCharType curDir = m_iDirOverride == FRIBIDI_TYPE_UNSET ? m_iDirection : m_iDirOverride; UT_ASSERT(curDir != FRIBIDI_TYPE_UNSET); if(curDir != prevDir) { clearScreen(); m_bRefreshDrawBuffer = true; if(m_pLine) { m_pLine->changeDirectionUsed(prevDir,curDir,true); //m_pLine->setNeedsRedraw(); } } //UT_DEBUGMSG(("TextRun::setDirection: direction=%d\n", m_iDirection)); } UT_UCSChar getMirrorChar(UT_UCSChar c) { //got to do this, otherwise bsearch screws up FriBidiChar fbc = (FriBidiChar) c, mfbc; if (fribidi_get_mirror_char (/* Input */ fbc, /* Output */&mfbc)) return (UT_UCSChar) mfbc; else return c; } void fp_TextRun::_refreshPrefs(UT_Worker * /*pWorker*/) { XAP_App::getApp()->getPrefsValueBool((XML_Char*)XAP_PREF_KEY_UseContextGlyphs, &s_bUseContextGlyphs); XAP_App::getApp()->getPrefsValueBool((XML_Char*)XAP_PREF_KEY_SaveContextGlyphs, &s_bSaveContextGlyphs); xxx_UT_DEBUGMSG(("fp_TextRun::_refreshPrefs: new values %d, %d\n", s_bUseContextGlyphs, s_bSaveContextGlyphs)); } /* NB !!! This function will set the m_iDirOverride member and change the properties in the piece table correspondingly; however, note that if dir == FRIBIDI_TYPE_UNSET, this function must immediately return. Because of this specialised behaviour and since it does not do any updates, etc., its usability is limited -- its main purpose is to allow to set this property in response to inserting a Unicode direction token in fl_BlockLayout _immediately_ after the run is created. For all other purposes one of the standard edit methods should be used. */ void fp_TextRun::setDirOverride(FriBidiCharType dir) { if(dir == FRIBIDI_TYPE_UNSET || dir == m_iDirOverride) return; const XML_Char * prop[] = {NULL, NULL, 0}; const XML_Char direction[] = "dir-override"; const XML_Char rtl[] = "rtl"; const XML_Char ltr[] = "ltr"; prop[0] = (XML_Char*) &direction; switch(dir) { case FRIBIDI_TYPE_LTR: prop[1] = (XML_Char*) <r; break; case FRIBIDI_TYPE_RTL: prop[1] = (XML_Char*) &rtl; break; default: UT_ASSERT(UT_SHOULD_NOT_HAPPEN); }; m_iDirOverride = dir; UT_uint32 offset = m_pBL->getPosition() + m_iOffsetFirst; getBlock()->getDocument()->changeSpanFmt(PTC_AddFmt,offset,offset + m_iLen,NULL,prop); UT_DEBUGMSG(("fp_TextRun::setDirOverride: offset=%d, len=%d, dir=\"%s\"\n", offset,m_iLen,prop[1])); } #ifdef SMART_RUN_MERGING void fp_TextRun::breakNeighborsAtDirBoundaries() { FriBidiCharType iPrevType, iType = FRIBIDI_TYPE_UNSET; FriBidiCharType iDirection = getDirection(); fp_TextRun *pNext = NULL, *pPrev = NULL, *pOtherHalf; PT_BlockOffset curOffset = 0; const UT_UCSChar* pSpan; UT_uint32 spanOffset = 0; UT_uint32 lenSpan = 0; if( getPrev() && getPrev()->getType() == FPRUN_TEXT && getPrev()->getVisDirection() != iDirection) { pPrev = static_cast(getPrev()); curOffset = pPrev->getBlockOffset() + pPrev->getLength() - 1; } while(pPrev) { m_pBL->getSpanPtr((UT_uint32) curOffset, &pSpan, &lenSpan); if ( pSpan == (UT_UCSChar *)NULL || !lenSpan ) break; iPrevType = fribidi_get_type((FriBidiChar)pSpan[0]); while(curOffset > pPrev->getBlockOffset() && !FRIBIDI_IS_STRONG(iType)) { curOffset--; m_pBL->getSpanPtr((UT_uint32) curOffset, &pSpan, &lenSpan); iType = fribidi_get_type((FriBidiChar)pSpan[0]); if(iType != iPrevType) { pPrev->split(curOffset+1); //now we want to reset the direction of the second half UT_ASSERT(pPrev->getNext()->getType() == FPRUN_TEXT); pOtherHalf = static_cast(pPrev->getNext()); pOtherHalf->setDirection(iPrevType, pOtherHalf->getDirOverride()); iPrevType = iType; // we do not want to break here, since pPrev still points to the // left part, so we can carry on leftwards } } if(FRIBIDI_IS_STRONG(iPrevType)) break; // if we got this far, the whole previous run is weak, so we want to // reset its direction and proceed with the run before it pPrev->setDirection(iPrevType, pPrev->getDirOverride()); if(pPrev->getPrev() && pPrev->getPrev()->getType() == FPRUN_TEXT) { pPrev = static_cast(pPrev->getPrev()); curOffset = pPrev->getBlockOffset() + pPrev->getLength() - 1; } else break; } // now do the same thing with the following run if( getNext() && getNext()->getType() == FPRUN_TEXT && getNext()->getVisDirection() != iDirection) { pNext = static_cast(getNext()); curOffset = pNext->getBlockOffset(); } iType = FRIBIDI_TYPE_UNSET; while(pNext) { m_pBL->getSpanPtr((UT_uint32) curOffset, &pSpan, &lenSpan); if ( pSpan == (UT_UCSChar *)NULL || !lenSpan ) break; iPrevType = fribidi_get_type((FriBidiChar)pSpan[0]); bool bDirSet = false; spanOffset = 0; while(curOffset + spanOffset < pNext->getBlockOffset() + pNext->getLength() - 1 && !FRIBIDI_IS_STRONG(iType)) { spanOffset++; if(spanOffset >= lenSpan) { curOffset += spanOffset; spanOffset = 0; m_pBL->getSpanPtr((UT_uint32) curOffset, &pSpan, &lenSpan); } iType = fribidi_get_type((FriBidiChar)pSpan[spanOffset]); if(iType != iPrevType) { pNext->split(curOffset + spanOffset); pNext->setDirection(iPrevType, pNext->getDirOverride()); // now set direction of the second half UT_ASSERT(pNext->getNext()->getType() == FPRUN_TEXT); pOtherHalf = static_cast(pNext->getNext()); pOtherHalf->setDirection(iType, pOtherHalf->getDirOverride()); bDirSet = true; iPrevType = iType; // not needed // since pNext points now to the left half, the right-ward processing // cannot continue, but insteds we need to proceed with the new run // on the right break; } } if(FRIBIDI_IS_STRONG(iPrevType)) break; // if we got this far, the whole next run is weak, so we want to // reset its direction, unless we split it, in which case it has already // been set // then proceed with the run after it if(!bDirSet) pNext->setDirection(iPrevType, pNext->getDirOverride()); if(pNext->getNext() && pNext->getNext()->getType() == FPRUN_TEXT) { pNext = static_cast(pNext->getNext()); curOffset = pNext->getBlockOffset(); } else break; } } void fp_TextRun::breakMeAtDirBoundaries(FriBidiCharType iNewOverride) { // we cannot use the draw buffer here because in case of ligatures it might // contain characters of misleading directional properties fp_TextRun * pRun = this; UT_uint32 iLen = m_iLen; // need to remember this, since m_iLen will change if we split PT_BlockOffset currOffset = m_iOffsetFirst; const UT_UCSChar* pSpan; UT_uint32 lenSpan = 0; UT_uint32 spanOffset = 0; FriBidiCharType iPrevType, iType = FRIBIDI_TYPE_UNSET; m_pBL->getSpanPtr((UT_uint32) currOffset, &pSpan, &lenSpan); iPrevType = iType = fribidi_get_type((FriBidiChar)pSpan[spanOffset]); while((currOffset + spanOffset) < (m_iOffsetFirst + iLen)) { while(iPrevType == iType && ((currOffset + spanOffset) < (m_iOffsetFirst + iLen - 1))) { spanOffset++; if(spanOffset >= lenSpan) { currOffset += spanOffset; spanOffset = 0; m_pBL->getSpanPtr((UT_uint32) currOffset, &pSpan, &lenSpan); } iType = fribidi_get_type((FriBidiChar)pSpan[spanOffset]); } // if we reached the end of the origianl run, then stop if((currOffset + spanOffset) >= (m_iOffsetFirst + iLen - 1)) { pRun->setDirection(iPrevType, iNewOverride); break; } // so we know where the continuos fragment ends ... pRun->split(currOffset + spanOffset); pRun->setDirection(iPrevType, iNewOverride); UT_ASSERT(pRun->getNext() && pRun->getNext()->getType() == FPRUN_TEXT); pRun = static_cast(pRun->getNext()); iPrevType = iType; } } #endif #endif