| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_vcl.hxx" |
| |
| #include <cstdio> |
| |
| #define _USE_MATH_DEFINES |
| #include <math.h> |
| #include <sal/alloca.h> |
| |
| #include <salgdi.hxx> |
| #include <sallayout.hxx> |
| |
| #include <basegfx/polygon/b2dpolypolygon.hxx> |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| #include <basegfx/matrix/b2dhommatrixtools.hxx> |
| |
| #include <i18npool/lang.h> |
| |
| #include <tools/debug.hxx> |
| |
| #include <limits.h> |
| |
| #if defined _MSC_VER |
| #pragma warning(push, 1) |
| #endif |
| #include <unicode/ubidi.h> |
| #include <unicode/uchar.h> |
| #if defined _MSC_VER |
| #pragma warning(pop) |
| #endif |
| |
| #include <algorithm> |
| |
| #ifdef DEBUG |
| //#define MULTI_SL_DEBUG |
| #endif |
| |
| #ifdef MULTI_SL_DEBUG |
| #include <string> |
| FILE * mslLogFile = NULL; |
| FILE * mslLog() |
| { |
| #ifdef MSC |
| std::string logFileName(getenv("TEMP")); |
| logFileName.append("\\msllayout.log"); |
| if (mslLogFile == NULL) mslLogFile = fopen(logFileName.c_str(),"w"); |
| else fflush(mslLogFile); |
| return mslLogFile; |
| #else |
| return stdout; |
| #endif |
| } |
| #endif |
| // ======================================================================= |
| |
| // TODO: ask the glyph directly, for now we need this method because of #i99367# |
| // true if a codepoint doesn't influence the logical text width |
| bool IsDiacritic( sal_UCS4 nChar ) |
| { |
| // shortcut abvious non-diacritics |
| if( nChar < 0x0300 ) |
| return false; |
| if( nChar >= 0x2100 ) |
| return false; |
| |
| // TODO: #i105058# use icu uchar.h's character classification instead of the handcrafted table |
| struct DiaRange { sal_UCS4 mnMin, mnEnd;}; |
| static const DiaRange aRanges[] = { |
| {0x0300, 0x0370}, |
| {0x0590, 0x05BE}, {0x05BF, 0x05C0}, {0x05C1, 0x05C3}, {0x05C4, 0x05C6}, {0x05C7, 0x05C8}, |
| {0x0610, 0x061B}, {0x064B, 0x0660}, {0x0670, 0x0671}, {0x06D6, 0x06DD}, {0x06DF, 0x06E5}, {0x06E7, 0x06E9}, {0x06EA,0x06EF}, |
| {0x0730, 0x074D}, {0x07A6, 0x07B1}, {0x07EB, 0x07F4}, |
| #if 0 // all known fonts have zero-width diacritics already, so no need to query it |
| {0x0900, 0x0904}, {0x093C, 0x093D}, {0x0941, 0x0948}, {0x094D, 0x0950}, {0x0951, 0x0958}, |
| {0x0980, 0x0985}, {0x09BC, 0x09BD}, {0x09C1, 0x09C7}, {0x09CD, 0x09CE}, {0x09E2, 0x09E6}, |
| {0x0A00, 0x0A05}, {0x0A3C, 0x0A59}, //... |
| #endif |
| {0x1DC0, 0x1E00}, |
| {0x205F, 0x2070}, {0x20D0, 0x2100}, |
| {0xFB1E, 0xFB1F} |
| }; |
| |
| // TODO: almost anything is faster than an O(n) search |
| static const int nCount = sizeof(aRanges) / sizeof(*aRanges); |
| const DiaRange* pRange = &aRanges[0]; |
| for( int i = nCount; --i >= 0; ++pRange ) |
| if( (pRange->mnMin <= nChar) && (nChar < pRange->mnEnd) ) |
| return true; |
| |
| return false; |
| } |
| |
| // ======================================================================= |
| |
| int GetVerticalFlags( sal_UCS4 nChar ) |
| { |
| if( (nChar >= 0x1100 && nChar <= 0x11f9) // Hangul Jamo |
| || (nChar == 0x2030 || nChar == 0x2031) // per mille sign |
| || (nChar >= 0x3000 && nChar <= 0xfaff) // unified CJK |
| || (nChar >= 0xfe20 && nChar <= 0xfe6f) // CJK compatibility |
| || (nChar >= 0xff00 && nChar <= 0xfffd) ) // other CJK |
| { |
| /* #i52932# remember: |
| nChar == 0x2010 || nChar == 0x2015 |
| nChar == 0x2016 || nChar == 0x2026 |
| are GF_NONE also, but already handled in the outer if condition |
| */ |
| if((nChar >= 0x3008 && nChar <= 0x301C && nChar != 0x3012) |
| || (nChar == 0xFF3B || nChar == 0xFF3D) |
| || (nChar >= 0xFF5B && nChar <= 0xFF9F) // halfwidth forms |
| || (nChar == 0xFFE3) ) |
| return GF_NONE; // not rotated |
| else if( nChar == 0x30fc ) |
| return GF_ROTR; // right |
| return GF_ROTL; // left |
| } |
| else if( (nChar >= 0x20000) && (nChar <= 0x3FFFF) ) // all SIP/TIP ideographs |
| return GF_ROTL; // left |
| |
| return GF_NONE; // not rotated as default |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| sal_UCS4 GetVerticalChar( sal_UCS4 ) |
| { |
| return 0; // #i14788# input method is responsible vertical char changes |
| |
| #if 0 |
| int nVert = 0; |
| switch( nChar ) |
| { |
| // #104627# special treatment for some unicodes |
| case 0x002C: nVert = 0x3001; break; |
| case 0x002E: nVert = 0x3002; break; |
| /* |
| // to few fonts have the compatibility forms, using |
| // them will then cause more trouble than good |
| // TODO: decide on a font specific basis |
| case 0x2018: nVert = 0xFE41; break; |
| case 0x2019: nVert = 0xFE42; break; |
| case 0x201C: nVert = 0xFE43; break; |
| case 0x201D: nVert = 0xFE44; break; |
| // CJK compatibility forms |
| case 0x2025: nVert = 0xFE30; break; |
| case 0x2014: nVert = 0xFE31; break; |
| case 0x2013: nVert = 0xFE32; break; |
| case 0x005F: nVert = 0xFE33; break; |
| case 0x0028: nVert = 0xFE35; break; |
| case 0x0029: nVert = 0xFE36; break; |
| case 0x007B: nVert = 0xFE37; break; |
| case 0x007D: nVert = 0xFE38; break; |
| case 0x3014: nVert = 0xFE39; break; |
| case 0x3015: nVert = 0xFE3A; break; |
| case 0x3010: nVert = 0xFE3B; break; |
| case 0x3011: nVert = 0xFE3C; break; |
| case 0x300A: nVert = 0xFE3D; break; |
| case 0x300B: nVert = 0xFE3E; break; |
| case 0x3008: nVert = 0xFE3F; break; |
| case 0x3009: nVert = 0xFE40; break; |
| case 0x300C: nVert = 0xFE41; break; |
| case 0x300D: nVert = 0xFE42; break; |
| case 0x300E: nVert = 0xFE43; break; |
| case 0x300F: nVert = 0xFE44; break; |
| */ |
| } |
| |
| return nVert; |
| #endif |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| VCL_DLLPUBLIC sal_UCS4 GetMirroredChar( sal_UCS4 nChar ) |
| { |
| nChar = u_charMirror( nChar ); |
| return nChar; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| // Get simple approximations for unicodes |
| const char* GetAutofallback( sal_UCS4 nChar ) |
| { |
| const char* pStr = NULL; |
| switch( nChar ) |
| { |
| case 0x01C0: |
| case 0x2223: |
| case 0x2758: |
| pStr = "|"; break; |
| case 0x02DC: |
| pStr = "~"; break; |
| case 0x037E: |
| pStr = ";"; break; |
| case 0x2000: |
| case 0x2001: |
| case 0x2002: |
| case 0x2003: |
| case 0x2004: |
| case 0x2005: |
| case 0x2006: |
| case 0x2007: |
| case 0x2008: |
| case 0x2009: |
| case 0x200A: |
| case 0x202F: |
| pStr = " "; break; |
| case 0x2010: |
| case 0x2011: |
| case 0x2012: |
| case 0x2013: |
| case 0x2014: |
| pStr = "-"; break; |
| case 0x2015: |
| pStr = "--"; break; |
| case 0x2016: |
| pStr = "||"; break; |
| case 0x2017: |
| pStr = "_"; break; |
| case 0x2018: |
| case 0x2019: |
| case 0x201B: |
| pStr = "\'"; break; |
| case 0x201A: |
| pStr = ","; break; |
| case 0x201C: |
| case 0x201D: |
| case 0x201E: |
| case 0x201F: |
| case 0x2033: |
| pStr = "\""; break; |
| case 0x2039: |
| pStr = "<"; break; |
| case 0x203A: |
| pStr = ">"; break; |
| case 0x203C: |
| pStr = "!!"; break; |
| case 0x203D: |
| pStr = "?"; break; |
| case 0x2044: |
| case 0x2215: |
| pStr = "/"; break; |
| case 0x2048: |
| pStr = "?!"; break; |
| case 0x2049: |
| pStr = "!?"; break; |
| case 0x2216: |
| pStr = "\\"; break; |
| case 0x2217: |
| pStr = "*"; break; |
| case 0x2236: |
| pStr = ":"; break; |
| case 0x2264: |
| pStr = "<="; break; |
| case 0x2265: |
| pStr = "<="; break; |
| case 0x2303: |
| pStr = "^"; break; |
| } |
| |
| return pStr; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang ) |
| { |
| // currently only conversion from ASCII digits is interesting |
| if( (nChar < '0') || ('9' < nChar) ) |
| return nChar; |
| |
| int nOffset; |
| // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region. |
| // CAVEAT! To some like Mongolian MS assigned the same primary language |
| // although the script type is different! |
| switch( eLang & LANGUAGE_MASK_PRIMARY ) |
| { |
| default: |
| nOffset = 0; |
| break; |
| case LANGUAGE_ARABIC_SAUDI_ARABIA & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0660 - '0'; // arabic-indic digits |
| break; |
| case LANGUAGE_FARSI & LANGUAGE_MASK_PRIMARY: |
| case LANGUAGE_URDU & LANGUAGE_MASK_PRIMARY: |
| case LANGUAGE_PUNJABI & LANGUAGE_MASK_PRIMARY: //??? |
| case LANGUAGE_SINDHI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x06F0 - '0'; // eastern arabic-indic digits |
| break; |
| case LANGUAGE_BENGALI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x09E6 - '0'; // bengali |
| break; |
| case LANGUAGE_HINDI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0966 - '0'; // devanagari |
| break; |
| case LANGUAGE_AMHARIC_ETHIOPIA & LANGUAGE_MASK_PRIMARY: |
| case LANGUAGE_TIGRIGNA_ETHIOPIA & LANGUAGE_MASK_PRIMARY: |
| // TODO case: |
| nOffset = 0x1369 - '0'; // ethiopic |
| break; |
| case LANGUAGE_GUJARATI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0AE6 - '0'; // gujarati |
| break; |
| #ifdef LANGUAGE_GURMUKHI // TODO case: |
| case LANGUAGE_GURMUKHI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0A66 - '0'; // gurmukhi |
| break; |
| #endif |
| case LANGUAGE_KANNADA & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0CE6 - '0'; // kannada |
| break; |
| case LANGUAGE_KHMER & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x17E0 - '0'; // khmer |
| break; |
| case LANGUAGE_LAO & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0ED0 - '0'; // lao |
| break; |
| case LANGUAGE_MALAYALAM & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0D66 - '0'; // malayalam |
| break; |
| case LANGUAGE_MONGOLIAN & LANGUAGE_MASK_PRIMARY: |
| if (eLang == LANGUAGE_MONGOLIAN_MONGOLIAN) |
| nOffset = 0x1810 - '0'; // mongolian |
| else |
| nOffset = 0; // mongolian cyrillic |
| break; |
| case LANGUAGE_BURMESE & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x1040 - '0'; // myanmar |
| break; |
| case LANGUAGE_ORIYA & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0B66 - '0'; // oriya |
| break; |
| case LANGUAGE_TAMIL & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0BE7 - '0'; // tamil |
| break; |
| case LANGUAGE_TELUGU & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0C66 - '0'; // telugu |
| break; |
| case LANGUAGE_THAI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0E50 - '0'; // thai |
| break; |
| case LANGUAGE_TIBETAN & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0F20 - '0'; // tibetan |
| break; |
| #if 0 // TODO: use language type for these digit substitutions? |
| // TODO case: |
| nOffset = 0x2776 - '0'; // dingbat circled |
| break; |
| // TODO case: |
| nOffset = 0x2070 - '0'; // superscript |
| break; |
| // TODO case: |
| nOffset = 0x2080 - '0'; // subscript |
| break; |
| #endif |
| } |
| |
| nChar += nOffset; |
| return nChar; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| inline bool IsControlChar( sal_UCS4 cChar ) |
| { |
| // C0 control characters |
| if( (0x0001 <= cChar) && (cChar <= 0x001F) ) |
| return true; |
| // formatting characters |
| if( (0x200E <= cChar) && (cChar <= 0x200F) ) |
| return true; |
| if( (0x2028 <= cChar) && (cChar <= 0x202E) ) |
| return true; |
| // deprecated formatting characters |
| if( (0x206A <= cChar) && (cChar <= 0x206F) ) |
| return true; |
| if( (0x2060 == cChar) ) |
| return true; |
| // byte order markers and invalid unicode |
| if( (cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF) ) |
| return true; |
| // variation selectors |
| if( (0xFE00 <= cChar) && (cChar <= 0xFE0F) ) |
| return true; |
| if( (0xE0100 <= cChar) && (cChar <= 0xE01EF) ) |
| return true; |
| return false; |
| } |
| |
| // ======================================================================= |
| |
| bool ImplLayoutRuns::AddPos( int nCharPos, bool bRTL ) |
| { |
| // check if charpos could extend current run |
| int nIndex = maRuns.size(); |
| if( nIndex >= 2 ) |
| { |
| int nRunPos0 = maRuns[ nIndex-2 ]; |
| int nRunPos1 = maRuns[ nIndex-1 ]; |
| if( ((nCharPos + bRTL) == nRunPos1) |
| && ((nRunPos0 > nRunPos1) == bRTL) ) |
| { |
| // extend current run by new charpos |
| maRuns[ nIndex-1 ] = nCharPos + !bRTL; |
| return false; |
| } |
| // ignore new charpos when it is in current run |
| if( (nRunPos0 <= nCharPos) && (nCharPos < nRunPos1) ) |
| return false; |
| if( (nRunPos1 <= nCharPos) && (nCharPos < nRunPos0) ) |
| return false; |
| } |
| |
| // else append a new run consisting of the new charpos |
| maRuns.push_back( nCharPos + (bRTL ? 1 : 0) ); |
| maRuns.push_back( nCharPos + (bRTL ? 0 : 1) ); |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool ImplLayoutRuns::AddRun( int nCharPos0, int nCharPos1, bool bRTL ) |
| { |
| if( nCharPos0 == nCharPos1 ) |
| return false; |
| |
| // swap if needed |
| if( bRTL == (nCharPos0 < nCharPos1) ) |
| { |
| int nTemp = nCharPos0; |
| nCharPos0 = nCharPos1; |
| nCharPos1 = nTemp; |
| } |
| |
| // append new run |
| maRuns.push_back( nCharPos0 ); |
| maRuns.push_back( nCharPos1 ); |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool ImplLayoutRuns::PosIsInRun( int nCharPos ) const |
| { |
| if( mnRunIndex >= (int)maRuns.size() ) |
| return false; |
| |
| int nMinCharPos = maRuns[ mnRunIndex+0 ]; |
| int nEndCharPos = maRuns[ mnRunIndex+1 ]; |
| if( nMinCharPos > nEndCharPos ) // reversed in RTL case |
| { |
| int nTemp = nMinCharPos; |
| nMinCharPos = nEndCharPos; |
| nEndCharPos = nTemp; |
| } |
| |
| if( nCharPos < nMinCharPos ) |
| return false; |
| if( nCharPos >= nEndCharPos ) |
| return false; |
| return true; |
| } |
| |
| bool ImplLayoutRuns::PosIsInAnyRun( int nCharPos ) const |
| { |
| bool bRet = false; |
| int nRunIndex = mnRunIndex; |
| |
| ImplLayoutRuns *pThis = const_cast<ImplLayoutRuns*>(this); |
| |
| pThis->ResetPos(); |
| |
| for (size_t i = 0; i < maRuns.size(); i+=2) |
| { |
| if( (bRet = PosIsInRun( nCharPos )) == true ) |
| break; |
| pThis->NextRun(); |
| } |
| |
| pThis->mnRunIndex = nRunIndex; |
| return bRet; |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| |
| bool ImplLayoutRuns::GetNextPos( int* nCharPos, bool* bRightToLeft ) |
| { |
| // negative nCharPos => reset to first run |
| if( *nCharPos < 0 ) |
| mnRunIndex = 0; |
| |
| // return false when all runs completed |
| if( mnRunIndex >= (int)maRuns.size() ) |
| return false; |
| |
| int nRunPos0 = maRuns[ mnRunIndex+0 ]; |
| int nRunPos1 = maRuns[ mnRunIndex+1 ]; |
| *bRightToLeft = (nRunPos0 > nRunPos1); |
| |
| if( *nCharPos < 0 ) |
| { |
| // get first valid nCharPos in run |
| *nCharPos = nRunPos0; |
| } |
| else |
| { |
| // advance to next nCharPos for LTR case |
| if( !*bRightToLeft ) |
| ++(*nCharPos); |
| |
| // advance to next run if current run is completed |
| if( *nCharPos == nRunPos1 ) |
| { |
| if( (mnRunIndex += 2) >= (int)maRuns.size() ) |
| return false; |
| nRunPos0 = maRuns[ mnRunIndex+0 ]; |
| nRunPos1 = maRuns[ mnRunIndex+1 ]; |
| *bRightToLeft = (nRunPos0 > nRunPos1); |
| *nCharPos = nRunPos0; |
| } |
| } |
| |
| // advance to next nCharPos for RTL case |
| if( *bRightToLeft ) |
| --(*nCharPos); |
| |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLeft ) const |
| { |
| if( mnRunIndex >= (int)maRuns.size() ) |
| return false; |
| |
| int nRunPos0 = maRuns[ mnRunIndex+0 ]; |
| int nRunPos1 = maRuns[ mnRunIndex+1 ]; |
| *bRightToLeft = (nRunPos1 < nRunPos0) ; |
| if( !*bRightToLeft ) |
| { |
| *nMinRunPos = nRunPos0; |
| *nEndRunPos = nRunPos1; |
| } |
| else |
| { |
| *nMinRunPos = nRunPos1; |
| *nEndRunPos = nRunPos0; |
| } |
| return true; |
| } |
| |
| // ======================================================================= |
| |
| ImplLayoutArgs::ImplLayoutArgs( const xub_Unicode* pStr, int nLen, |
| int nMinCharPos, int nEndCharPos, int nFlags ) |
| : |
| mnFlags( nFlags ), |
| mnLength( nLen ), |
| mnMinCharPos( nMinCharPos ), |
| mnEndCharPos( nEndCharPos ), |
| mpStr( pStr ), |
| mpDXArray( NULL ), |
| mnLayoutWidth( 0 ), |
| mnOrientation( 0 ) |
| { |
| if( mnFlags & SAL_LAYOUT_BIDI_STRONG ) |
| { |
| // handle strong BiDi mode |
| |
| // do not bother to BiDi analyze strong LTR/RTL |
| // TODO: can we assume these strings do not have unicode control chars? |
| // if not remove the control characters from the runs |
| bool bRTL = ((mnFlags & SAL_LAYOUT_BIDI_RTL) != 0); |
| AddRun( mnMinCharPos, mnEndCharPos, bRTL ); |
| } |
| else |
| { |
| // handle weak BiDi mode |
| |
| UBiDiLevel nLevel = UBIDI_DEFAULT_LTR; |
| if( mnFlags & SAL_LAYOUT_BIDI_RTL ) |
| nLevel = UBIDI_DEFAULT_RTL; |
| |
| // prepare substring for BiDi analysis |
| // TODO: reuse allocated pParaBidi |
| UErrorCode rcI18n = U_ZERO_ERROR; |
| UBiDi* pParaBidi = ubidi_openSized( mnLength, 0, &rcI18n ); |
| if( !pParaBidi ) |
| return; |
| ubidi_setPara( pParaBidi, reinterpret_cast<const UChar *>(mpStr), mnLength, nLevel, NULL, &rcI18n ); // UChar != sal_Unicode in MinGW |
| |
| UBiDi* pLineBidi = pParaBidi; |
| int nSubLength = mnEndCharPos - mnMinCharPos; |
| if( nSubLength != mnLength ) |
| { |
| pLineBidi = ubidi_openSized( nSubLength, 0, &rcI18n ); |
| ubidi_setLine( pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n ); |
| } |
| |
| // run BiDi algorithm |
| const int nRunCount = ubidi_countRuns( pLineBidi, &rcI18n ); |
| //maRuns.resize( 2 * nRunCount ); |
| for( int i = 0; i < nRunCount; ++i ) |
| { |
| int32_t nMinPos, nLength; |
| const UBiDiDirection nDir = ubidi_getVisualRun( pLineBidi, i, &nMinPos, &nLength ); |
| const int nPos0 = nMinPos + mnMinCharPos; |
| const int nPos1 = nPos0 + nLength; |
| |
| const bool bRTL = (nDir == UBIDI_RTL); |
| AddRun( nPos0, nPos1, bRTL ); |
| } |
| |
| // cleanup BiDi engine |
| if( pLineBidi != pParaBidi ) |
| ubidi_close( pLineBidi ); |
| ubidi_close( pParaBidi ); |
| } |
| |
| // prepare calls to GetNextPos/GetNextRun |
| maRuns.ResetPos(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| // add a run after splitting it up to get rid of control chars |
| void ImplLayoutArgs::AddRun( int nCharPos0, int nCharPos1, bool bRTL ) |
| { |
| DBG_ASSERT( nCharPos0 <= nCharPos1, "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1" ); |
| |
| // remove control characters from runs by splitting them up |
| if( !bRTL ) |
| { |
| for( int i = nCharPos0; i < nCharPos1; ++i ) |
| if( IsControlChar( mpStr[i] ) ) |
| { |
| // add run until control char |
| maRuns.AddRun( nCharPos0, i, bRTL ); |
| nCharPos0 = i + 1; |
| } |
| } |
| else |
| { |
| for( int i = nCharPos1; --i >= nCharPos0; ) |
| if( IsControlChar( mpStr[i] ) ) |
| { |
| // add run until control char |
| maRuns.AddRun( i+1, nCharPos1, bRTL ); |
| nCharPos1 = i; |
| } |
| } |
| |
| // add remainder of run |
| maRuns.AddRun( nCharPos0, nCharPos1, bRTL ); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool ImplLayoutArgs::PrepareFallback() |
| { |
| // short circuit if no fallback is needed |
| if( maReruns.IsEmpty() ) |
| { |
| maRuns.Clear(); |
| return false; |
| } |
| |
| // convert the fallback requests to layout requests |
| bool bRTL; |
| int nMin, nEnd; |
| |
| // get the individual fallback requests |
| typedef std::vector<int> IntVector; |
| IntVector aPosVector; |
| aPosVector.reserve( mnLength ); |
| maReruns.ResetPos(); |
| for(; maReruns.GetRun( &nMin, &nEnd, &bRTL ); maReruns.NextRun() ) |
| for( int i = nMin; i < nEnd; ++i ) |
| aPosVector.push_back( i ); |
| maReruns.Clear(); |
| |
| // sort the individual fallback requests |
| std::sort( aPosVector.begin(), aPosVector.end() ); |
| |
| // adjust fallback runs to have the same order and limits of the original runs |
| ImplLayoutRuns aNewRuns; |
| maRuns.ResetPos(); |
| for(; maRuns.GetRun( &nMin, &nEnd, &bRTL ); maRuns.NextRun() ) |
| { |
| if( !bRTL) { |
| IntVector::const_iterator it = std::lower_bound( aPosVector.begin(), aPosVector.end(), nMin ); |
| for(; (it != aPosVector.end()) && (*it < nEnd); ++it ) |
| aNewRuns.AddPos( *it, bRTL ); |
| } else { |
| IntVector::const_iterator it = std::upper_bound( aPosVector.begin(), aPosVector.end(), nEnd ); |
| while( (it != aPosVector.begin()) && (*--it >= nMin) ) |
| aNewRuns.AddPos( *it, bRTL ); |
| } |
| } |
| |
| maRuns = aNewRuns; // TODO: use vector<>::swap() |
| maRuns.ResetPos(); |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool ImplLayoutArgs::GetNextRun( int* nMinRunPos, int* nEndRunPos, bool* bRTL ) |
| { |
| bool bValid = maRuns.GetRun( nMinRunPos, nEndRunPos, bRTL ); |
| maRuns.NextRun(); |
| return bValid; |
| } |
| |
| // ======================================================================= |
| |
| SalLayout::SalLayout() |
| : mnMinCharPos( -1 ), |
| mnEndCharPos( -1 ), |
| mnLayoutFlags( 0 ), |
| mnUnitsPerPixel( 1 ), |
| mnOrientation( 0 ), |
| mnRefCount( 1 ), |
| maDrawOffset( 0, 0 ) |
| {} |
| |
| // ----------------------------------------------------------------------- |
| |
| SalLayout::~SalLayout() |
| {} |
| |
| // ----------------------------------------------------------------------- |
| |
| void SalLayout::AdjustLayout( ImplLayoutArgs& rArgs ) |
| { |
| mnMinCharPos = rArgs.mnMinCharPos; |
| mnEndCharPos = rArgs.mnEndCharPos; |
| mnLayoutFlags = rArgs.mnFlags; |
| mnOrientation = rArgs.mnOrientation; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void SalLayout::Reference() const |
| { |
| // TODO: protect when multiple threads can access this |
| ++mnRefCount; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void SalLayout::Release() const |
| { |
| // TODO: protect when multiple threads can access this |
| if( --mnRefCount > 0 ) |
| return; |
| // const_cast because some compilers violate ANSI C++ spec |
| delete const_cast<SalLayout*>(this); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| Point SalLayout::GetDrawPosition( const Point& rRelative ) const |
| { |
| Point aPos = maDrawBase; |
| Point aOfs = rRelative + maDrawOffset; |
| |
| if( mnOrientation == 0 ) |
| aPos += aOfs; |
| else |
| { |
| // cache trigonometric results |
| static int nOldOrientation = 0; |
| static double fCos = 1.0, fSin = 0.0; |
| if( nOldOrientation != mnOrientation ) |
| { |
| nOldOrientation = mnOrientation; |
| double fRad = mnOrientation * (M_PI / 1800.0); |
| fCos = cos( fRad ); |
| fSin = sin( fRad ); |
| } |
| |
| double fX = aOfs.X(); |
| double fY = aOfs.Y(); |
| long nX = static_cast<long>( +fCos * fX + fSin * fY ); |
| long nY = static_cast<long>( +fCos * fY - fSin * fX ); |
| aPos += Point( nX, nY ); |
| } |
| |
| return aPos; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| // returns asian kerning values in quarter of character width units |
| // to enable automatic halfwidth substitution for fullwidth punctuation |
| // return value is negative for l, positive for r, zero for neutral |
| |
| // If the range doesn't match in 0x3000 and 0x30FB, please change |
| // also ImplCalcKerning. |
| |
| int SalLayout::CalcAsianKerning( sal_UCS4 c, bool bLeft, bool /*TODO:? bVertical*/ ) |
| { |
| // http://www.asahi-net.or.jp/~sd5a-ucd/freetexts/jis/x4051/1995/appendix.html |
| static signed char nTable[0x30] = |
| { |
| 0, -2, -2, 0, 0, 0, 0, 0, +2, -2, +2, -2, +2, -2, +2, -2, |
| +2, -2, 0, 0, +2, -2, +2, -2, 0, 0, 0, 0, 0, +2, -2, -2, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, +2, +2, -2, -2 |
| }; |
| |
| int nResult = 0; |
| if( (c >= 0x3000) && (c < 0x3030) ) |
| nResult = nTable[ c - 0x3000 ]; |
| else switch( c ) |
| { |
| #if 0 // TODO: enable it for real-fixed-width fonts? |
| case ':': case ';': case '!': |
| if( !bVertical ) |
| nResult = bLeft ? -1 : +1; // 25% left and right |
| break; |
| #endif |
| case 0x30FB: |
| nResult = bLeft ? -1 : +1; // 25% left/right/top/bottom |
| break; |
| case 0x2019: case 0x201D: |
| case 0xFF01: case 0xFF09: case 0xFF0C: |
| case 0xFF1A: case 0xFF1B: |
| nResult = -2; |
| break; |
| case 0x2018: case 0x201C: |
| case 0xFF08: |
| nResult = +2; |
| break; |
| default: |
| break; |
| } |
| |
| return nResult; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool SalLayout::GetOutline( SalGraphics& rSalGraphics, |
| ::basegfx::B2DPolyPolygonVector& rVector ) const |
| { |
| bool bAllOk = true; |
| bool bOneOk = false; |
| |
| Point aPos; |
| ::basegfx::B2DPolyPolygon aGlyphOutline; |
| for( int nStart = 0;;) |
| { |
| sal_GlyphId nLGlyph; |
| if( !GetNextGlyphs( 1, &nLGlyph, aPos, nStart ) ) |
| break; |
| |
| // get outline of individual glyph, ignoring "empty" glyphs |
| bool bSuccess = rSalGraphics.GetGlyphOutline( nLGlyph, aGlyphOutline ); |
| bAllOk &= bSuccess; |
| bOneOk |= bSuccess; |
| // only add non-empty outlines |
| if( bSuccess && (aGlyphOutline.count() > 0) ) |
| { |
| if( aPos.X() || aPos.Y() ) |
| { |
| aGlyphOutline.transform(basegfx::tools::createTranslateB2DHomMatrix(aPos.X(), aPos.Y())); |
| } |
| |
| // insert outline at correct position |
| rVector.push_back( aGlyphOutline ); |
| } |
| } |
| |
| return (bAllOk & bOneOk); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool SalLayout::GetBoundRect( SalGraphics& rSalGraphics, Rectangle& rRect ) const |
| { |
| bool bRet = false; |
| rRect.SetEmpty(); |
| |
| Point aPos; |
| Rectangle aRectangle; |
| for( int nStart = 0;;) |
| { |
| sal_GlyphId nLGlyph; |
| if( !GetNextGlyphs( 1, &nLGlyph, aPos, nStart ) ) |
| break; |
| |
| // get bounding rectangle of individual glyph |
| if( rSalGraphics.GetGlyphBoundRect( nLGlyph, aRectangle ) ) |
| { |
| // merge rectangle |
| aRectangle += aPos; |
| rRect.Union( aRectangle ); |
| bRet = true; |
| } |
| } |
| |
| return bRet; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool SalLayout::IsSpacingGlyph( sal_GlyphId nGlyph ) const |
| { |
| bool bRet = false; |
| if( nGlyph & GF_ISCHAR ) |
| { |
| long nChar = nGlyph & GF_IDXMASK; |
| bRet = (nChar <= 0x0020) // blank |
| //|| (nChar == 0x00A0) // non breaking space |
| || (nChar >= 0x2000 && nChar <= 0x200F) // whitespace |
| || (nChar == 0x3000); // ideographic space |
| } |
| else |
| bRet = ((nGlyph & GF_IDXMASK) == 3); |
| return bRet; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| const ImplFontData* SalLayout::GetFallbackFontData( sal_GlyphId /*aGlyphId*/ ) const |
| { |
| #if 0 |
| int nFallbackLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT |
| assert( nFallbackLevel == 0 ); |
| #endif |
| return NULL; |
| } |
| |
| // ======================================================================= |
| |
| GenericSalLayout::GenericSalLayout() |
| : mpGlyphItems(0), |
| mnGlyphCount(0), |
| mnGlyphCapacity(0) |
| {} |
| |
| // ----------------------------------------------------------------------- |
| |
| GenericSalLayout::~GenericSalLayout() |
| { |
| delete[] mpGlyphItems; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::AppendGlyph( const GlyphItem& rGlyphItem ) |
| { |
| // TODO: use std::list<GlyphItem> |
| if( mnGlyphCount >= mnGlyphCapacity ) |
| { |
| mnGlyphCapacity += 16 + 3 * mnGlyphCount; |
| GlyphItem* pNewGI = new GlyphItem[ mnGlyphCapacity ]; |
| if( mpGlyphItems ) |
| { |
| for( int i = 0; i < mnGlyphCount; ++i ) |
| pNewGI[ i ] = mpGlyphItems[ i ]; |
| delete[] mpGlyphItems; |
| } |
| mpGlyphItems = pNewGI; |
| } |
| |
| mpGlyphItems[ mnGlyphCount++ ] = rGlyphItem; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool GenericSalLayout::GetCharWidths( sal_Int32* pCharWidths ) const |
| { |
| // initialize character extents buffer |
| int nCharCount = mnEndCharPos - mnMinCharPos; |
| for( int n = 0; n < nCharCount; ++n ) |
| pCharWidths[n] = 0; |
| |
| // determine cluster extents |
| const GlyphItem* const pEnd = mpGlyphItems + mnGlyphCount; |
| for( const GlyphItem* pG = mpGlyphItems; pG < pEnd; ++pG ) |
| { |
| // use cluster start to get char index |
| if( !pG->IsClusterStart() ) |
| continue; |
| |
| int n = pG->mnCharPos; |
| if( n >= mnEndCharPos ) |
| continue; |
| n -= mnMinCharPos; |
| if( n < 0 ) |
| continue; |
| |
| // left glyph in cluster defines default extent |
| long nXPosMin = pG->maLinearPos.X(); |
| long nXPosMax = nXPosMin + pG->mnNewWidth; |
| |
| // calculate right x-position for this glyph cluster |
| // break if no more glyphs in layout |
| // break at next glyph cluster start |
| while( (pG+1 < pEnd) && !pG[1].IsClusterStart() ) |
| { |
| // advance to next glyph in cluster |
| ++pG; |
| |
| if( pG->IsDiacritic() ) |
| continue; // ignore diacritics |
| // get leftmost x-extent of this glyph |
| long nXPos = pG->maLinearPos.X(); |
| if( nXPosMin > nXPos ) |
| nXPosMin = nXPos; |
| |
| // get rightmost x-extent of this glyph |
| nXPos += pG->mnNewWidth; |
| if( nXPosMax < nXPos ) |
| nXPosMax = nXPos; |
| } |
| |
| // when the current cluster overlaps with the next one assume |
| // rightmost cluster edge is the leftmost edge of next cluster |
| // for clusters that do not have x-sorted glyphs |
| // TODO: avoid recalculation of left bound in next cluster iteration |
| for( const GlyphItem* pN = pG; ++pN < pEnd; ) |
| { |
| if( pN->IsClusterStart() ) |
| break; |
| if( pN->IsDiacritic() ) |
| continue; // ignore diacritics |
| if( nXPosMax > pN->maLinearPos.X() ) |
| nXPosMax = pN->maLinearPos.X(); |
| } |
| if( nXPosMax < nXPosMin ) |
| nXPosMin = nXPosMax = 0; |
| |
| // character width is sum of glyph cluster widths |
| pCharWidths[n] += nXPosMax - nXPosMin; |
| } |
| |
| // TODO: distribute the cluster width proportionally to the characters |
| // clusters (e.g. ligatures) correspond to more than one char index, |
| // so some character widths are still uninitialized. This is solved |
| // by setting the first charwidth of the cluster to the cluster width |
| |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| long GenericSalLayout::FillDXArray( sal_Int32* pCharWidths ) const |
| { |
| if( pCharWidths ) |
| if( !GetCharWidths( pCharWidths ) ) |
| return 0; |
| |
| long nWidth = GetTextWidth(); |
| return nWidth; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| // the text width is the maximum logical extent of all glyphs |
| long GenericSalLayout::GetTextWidth() const |
| { |
| if( mnGlyphCount <= 0 ) |
| return 0; |
| |
| // initialize the extent |
| long nMinPos = 0; |
| long nMaxPos = 0; |
| |
| const GlyphItem* pG = mpGlyphItems; |
| for( int i = mnGlyphCount; --i >= 0; ++pG ) |
| { |
| // update the text extent with the glyph extent |
| long nXPos = pG->maLinearPos.X(); |
| if( nMinPos > nXPos ) |
| nMinPos = nXPos; |
| nXPos += pG->mnNewWidth; |
| if( nMaxPos < nXPos ) |
| nMaxPos = nXPos; |
| } |
| |
| long nWidth = nMaxPos - nMinPos; |
| return nWidth; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::AdjustLayout( ImplLayoutArgs& rArgs ) |
| { |
| SalLayout::AdjustLayout( rArgs ); |
| |
| if( rArgs.mpDXArray ) |
| ApplyDXArray( rArgs ); |
| else if( rArgs.mnLayoutWidth ) |
| Justify( rArgs.mnLayoutWidth ); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::ApplyDXArray( ImplLayoutArgs& rArgs ) |
| { |
| if( mnGlyphCount <= 0 ) |
| return; |
| |
| // determine cluster boundaries and x base offset |
| const int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos; |
| int* pLogCluster = (int*)alloca( nCharCount * sizeof(int) ); |
| int i, n; |
| long nBasePointX = -1; |
| if( mnLayoutFlags & SAL_LAYOUT_FOR_FALLBACK ) |
| nBasePointX = 0; |
| for( i = 0; i < nCharCount; ++i ) |
| pLogCluster[ i ] = -1; |
| GlyphItem* pG = mpGlyphItems; |
| for( i = 0; i < mnGlyphCount; ++i, ++pG ) |
| { |
| n = pG->mnCharPos - rArgs.mnMinCharPos; |
| if( (n < 0) || (nCharCount <= n) ) |
| continue; |
| if( pLogCluster[ n ] < 0 ) |
| pLogCluster[ n ] = i; |
| if( nBasePointX < 0 ) |
| nBasePointX = pG->maLinearPos.X(); |
| } |
| // retarget unresolved pLogCluster[n] to a glyph inside the cluster |
| // TODO: better do it while the deleted-glyph markers are still there |
| for( n = 0; n < nCharCount; ++n ) |
| if( (i = pLogCluster[0]) >= 0 ) |
| break; |
| if( n >= nCharCount ) |
| return; |
| for( n = 0; n < nCharCount; ++n ) |
| { |
| if( pLogCluster[ n ] < 0 ) |
| pLogCluster[ n ] = i; |
| else |
| i = pLogCluster[ n ]; |
| } |
| |
| // calculate adjusted cluster widths |
| sal_Int32* pNewGlyphWidths = (sal_Int32*)alloca( mnGlyphCount * sizeof(sal_Int32) ); |
| for( i = 0; i < mnGlyphCount; ++i ) |
| pNewGlyphWidths[ i ] = 0; |
| |
| bool bRTL; |
| for( int nCharPos = i = -1; rArgs.GetNextPos( &nCharPos, &bRTL ); ) |
| { |
| n = nCharPos - rArgs.mnMinCharPos; |
| if( (n < 0) || (nCharCount <= n) ) continue; |
| |
| if( pLogCluster[ n ] >= 0 ) |
| i = pLogCluster[ n ]; |
| if( i >= 0 ) |
| { |
| long nDelta = rArgs.mpDXArray[ n ] ; |
| if( n > 0 ) |
| nDelta -= rArgs.mpDXArray[ n-1 ]; |
| pNewGlyphWidths[ i ] += nDelta * mnUnitsPerPixel; |
| } |
| } |
| |
| // move cluster positions using the adjusted widths |
| long nDelta = 0; |
| long nNewPos = 0; |
| pG = mpGlyphItems; |
| for( i = 0; i < mnGlyphCount; ++i, ++pG ) |
| { |
| if( pG->IsClusterStart() ) |
| { |
| // calculate original and adjusted cluster width |
| int nOldClusterWidth = pG->mnNewWidth; |
| int nNewClusterWidth = pNewGlyphWidths[i]; |
| GlyphItem* pClusterG = pG + 1; |
| for( int j = i; ++j < mnGlyphCount; ++pClusterG ) |
| { |
| if( pClusterG->IsClusterStart() ) |
| break; |
| if( !pClusterG->IsDiacritic() ) // #i99367# ignore diacritics |
| nOldClusterWidth += pClusterG->mnNewWidth; |
| nNewClusterWidth += pNewGlyphWidths[j]; |
| } |
| const int nDiff = nNewClusterWidth - nOldClusterWidth; |
| |
| // adjust cluster glyph widths and positions |
| nDelta = nBasePointX + (nNewPos - pG->maLinearPos.X()); |
| if( !pG->IsRTLGlyph() ) |
| { |
| // for LTR case extend rightmost glyph in cluster |
| pClusterG[-1].mnNewWidth += nDiff; |
| } |
| else |
| { |
| // right align cluster in new space for RTL case |
| pG->mnNewWidth += nDiff; |
| nDelta += nDiff; |
| } |
| |
| nNewPos += nNewClusterWidth; |
| } |
| |
| pG->maLinearPos.X() += nDelta; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::Justify( long nNewWidth ) |
| { |
| nNewWidth *= mnUnitsPerPixel; |
| int nOldWidth = GetTextWidth(); |
| if( !nOldWidth || nNewWidth==nOldWidth ) |
| return; |
| |
| // find rightmost glyph, it won't get stretched |
| GlyphItem* pGRight = mpGlyphItems + mnGlyphCount - 1; |
| |
| // count stretchable glyphs |
| GlyphItem* pG; |
| int nStretchable = 0; |
| int nMaxGlyphWidth = 0; |
| for( pG = mpGlyphItems; pG < pGRight; ++pG ) |
| { |
| if( !pG->IsDiacritic() ) |
| ++nStretchable; |
| if( nMaxGlyphWidth < pG->mnOrigWidth ) |
| nMaxGlyphWidth = pG->mnOrigWidth; |
| } |
| |
| // move rightmost glyph to requested position |
| nOldWidth -= pGRight->mnOrigWidth; |
| if( nOldWidth <= 0 ) |
| return; |
| if( nNewWidth < nMaxGlyphWidth) |
| nNewWidth = nMaxGlyphWidth; |
| nNewWidth -= pGRight->mnOrigWidth; |
| pGRight->maLinearPos.X() = maBasePoint.X() + nNewWidth; |
| |
| // justify glyph widths and positions |
| int nDiffWidth = nNewWidth - nOldWidth; |
| if( nDiffWidth >= 0) // expanded case |
| { |
| // expand width by distributing space between glyphs evenly |
| int nDeltaSum = 0; |
| for( pG = mpGlyphItems; pG < pGRight; ++pG ) |
| { |
| // move glyph to justified position |
| pG->maLinearPos.X() += nDeltaSum; |
| |
| // do not stretch non-stretchable glyphs |
| if( pG->IsDiacritic() || (nStretchable <= 0) ) |
| continue; |
| |
| // distribute extra space equally to stretchable glyphs |
| int nDeltaWidth = nDiffWidth / nStretchable--; |
| nDiffWidth -= nDeltaWidth; |
| pG->mnNewWidth += nDeltaWidth; |
| nDeltaSum += nDeltaWidth; |
| } |
| } |
| else // condensed case |
| { |
| // squeeze width by moving glyphs proportionally |
| double fSqueeze = (double)nNewWidth / nOldWidth; |
| for( pG = mpGlyphItems; ++pG < pGRight;) |
| { |
| int nX = pG->maLinearPos.X() - maBasePoint.X(); |
| nX = (int)(nX * fSqueeze); |
| pG->maLinearPos.X() = nX + maBasePoint.X(); |
| } |
| // adjust glyph widths to new positions |
| for( pG = mpGlyphItems; pG < pGRight; ++pG ) |
| pG->mnNewWidth = pG[1].maLinearPos.X() - pG[0].maLinearPos.X(); |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::ApplyAsianKerning( const sal_Unicode* pStr, int nLength ) |
| { |
| long nOffset = 0; |
| |
| GlyphItem* pGEnd = mpGlyphItems + mnGlyphCount; |
| for( GlyphItem* pG = mpGlyphItems; pG < pGEnd; ++pG ) |
| { |
| const int n = pG->mnCharPos; |
| if( n < nLength - 1) |
| { |
| // ignore code ranges that are not affected by asian punctuation compression |
| const sal_Unicode cHere = pStr[n]; |
| if( ((0x3000 != (cHere & 0xFF00)) && (0x2010 != (cHere & 0xFFF0))) || (0xFF00 != (cHere & 0xFF00)) ) |
| continue; |
| const sal_Unicode cNext = pStr[n+1]; |
| if( ((0x3000 != (cNext & 0xFF00)) && (0x2010 != (cNext & 0xFFF0))) || (0xFF00 != (cNext & 0xFF00)) ) |
| continue; |
| |
| // calculate compression values |
| const bool bVertical = false; |
| long nKernFirst = +CalcAsianKerning( cHere, true, bVertical ); |
| long nKernNext = -CalcAsianKerning( cNext, false, bVertical ); |
| |
| // apply punctuation compression to logical glyph widths |
| long nDelta = (nKernFirst < nKernNext) ? nKernFirst : nKernNext; |
| if( nDelta<0 && nKernFirst!=0 && nKernNext!=0 ) |
| { |
| int nGlyphWidth = pG->mnOrigWidth; |
| nDelta = (nDelta * nGlyphWidth + 2) / 4; |
| if( pG+1 == pGEnd ) |
| pG->mnNewWidth += nDelta; |
| nOffset += nDelta; |
| } |
| } |
| |
| // adjust the glyph positions to the new glyph widths |
| if( pG+1 != pGEnd ) |
| pG->maLinearPos.X() += nOffset; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::KashidaJustify( long nKashidaIndex, int nKashidaWidth ) |
| { |
| // TODO: reimplement method when container type for GlyphItems changes |
| |
| // skip if the kashida glyph in the font looks suspicious |
| if( nKashidaWidth <= 0 ) |
| return; |
| |
| // calculate max number of needed kashidas |
| const GlyphItem* pG1 = mpGlyphItems; |
| int nKashidaCount = 0, i; |
| for( i = 0; i < mnGlyphCount; ++i, ++pG1 ) |
| { |
| // only inject kashidas in RTL contexts |
| if( !pG1->IsRTLGlyph() ) |
| continue; |
| // no kashida-injection for blank justified expansion either |
| if( IsSpacingGlyph( pG1->maGlyphId ) ) |
| continue; |
| |
| // calculate gap, ignore if too small |
| const int nGapWidth = pG1->mnNewWidth - pG1->mnOrigWidth; |
| // worst case is one kashida even for mini-gaps |
| if( 3 * nGapWidth >= nKashidaWidth ) |
| nKashidaCount += 1 + (nGapWidth / nKashidaWidth); |
| } |
| |
| if( !nKashidaCount ) |
| return; |
| |
| // reallocate glyph array for additional kashidas |
| // TODO: reuse array if additional glyphs would fit |
| mnGlyphCapacity = mnGlyphCount + nKashidaCount; |
| GlyphItem* pNewGlyphItems = new GlyphItem[ mnGlyphCapacity ]; |
| GlyphItem* pG2 = pNewGlyphItems; |
| pG1 = mpGlyphItems; |
| for( i = mnGlyphCount; --i >= 0; ++pG1, ++pG2 ) |
| { |
| // default action is to copy array element |
| *pG2 = *pG1; |
| |
| // only inject kashida in RTL contexts |
| if( !pG1->IsRTLGlyph() ) |
| continue; |
| // no kashida-injection for blank justified expansion either |
| if( IsSpacingGlyph( pG1->maGlyphId ) ) |
| continue; |
| |
| // calculate gap, skip if too small |
| int nGapWidth = pG1->mnNewWidth - pG1->mnOrigWidth; |
| if( 3*nGapWidth < nKashidaWidth ) |
| continue; |
| |
| // fill gap with kashidas |
| nKashidaCount = 0; |
| Point aPos = pG1->maLinearPos; |
| aPos.X() -= nGapWidth; // cluster is already right aligned |
| for(; nGapWidth > 0; nGapWidth -= nKashidaWidth, ++nKashidaCount ) |
| { |
| *(pG2++) = GlyphItem( pG1->mnCharPos, nKashidaIndex, aPos, |
| GlyphItem::IS_IN_CLUSTER|GlyphItem::IS_RTL_GLYPH, nKashidaWidth ); |
| aPos.X() += nKashidaWidth; |
| } |
| |
| // fixup rightmost kashida for gap remainder |
| if( nGapWidth < 0 ) |
| { |
| aPos.X() += nGapWidth; |
| if( nKashidaCount <= 1 ) |
| nGapWidth /= 2; // for small gap move kashida to middle |
| pG2[-1].mnNewWidth += nGapWidth; // adjust kashida width to gap width |
| pG2[-1].maLinearPos.X() += nGapWidth; |
| } |
| |
| // when kashidas were inserted move the original cluster |
| // to the right and shrink it to it's original width |
| *pG2 = *pG1; |
| pG2->maLinearPos.X() = aPos.X(); |
| pG2->mnNewWidth = pG2->mnOrigWidth; |
| } |
| |
| // use the new glyph array |
| DBG_ASSERT( mnGlyphCapacity >= pG2-pNewGlyphItems, "KashidaJustify overflow" ); |
| delete[] mpGlyphItems; |
| mpGlyphItems = pNewGlyphItems; |
| mnGlyphCount = pG2 - pNewGlyphItems; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const |
| { |
| // initialize result array |
| long nXPos = -1; |
| int i; |
| for( i = 0; i < nMaxIndex; ++i ) |
| pCaretXArray[ i ] = nXPos; |
| |
| // calculate caret positions using glyph array |
| const GlyphItem* pG = mpGlyphItems; |
| for( i = mnGlyphCount; --i >= 0; ++pG ) |
| { |
| nXPos = pG->maLinearPos.X(); |
| long nXRight = nXPos + pG->mnOrigWidth; |
| int n = pG->mnCharPos; |
| int nCurrIdx = 2 * (n - mnMinCharPos); |
| if( !pG->IsRTLGlyph() ) |
| { |
| // normal positions for LTR case |
| pCaretXArray[ nCurrIdx ] = nXPos; |
| pCaretXArray[ nCurrIdx+1 ] = nXRight; |
| } |
| else |
| { |
| // reverse positions for RTL case |
| pCaretXArray[ nCurrIdx ] = nXRight; |
| pCaretXArray[ nCurrIdx+1 ] = nXPos; |
| } |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| int GenericSalLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const |
| { |
| int nCharCapacity = mnEndCharPos - mnMinCharPos; |
| sal_Int32* pCharWidths = (sal_Int32*)alloca( nCharCapacity * sizeof(sal_Int32) ); |
| if( !GetCharWidths( pCharWidths ) ) |
| return STRING_LEN; |
| |
| long nWidth = 0; |
| for( int i = mnMinCharPos; i < mnEndCharPos; ++i ) |
| { |
| nWidth += pCharWidths[ i - mnMinCharPos ] * nFactor; |
| if( nWidth >= nMaxWidth ) |
| return i; |
| nWidth += nCharExtra; |
| } |
| |
| return STRING_LEN; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| int GenericSalLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos, |
| int& nStart, sal_Int32* pGlyphAdvAry, int* pCharPosAry ) const |
| { |
| const GlyphItem* pG = mpGlyphItems + nStart; |
| |
| // find next glyph in substring |
| for(; nStart < mnGlyphCount; ++nStart, ++pG ) |
| { |
| int n = pG->mnCharPos; |
| if( (mnMinCharPos <= n) && (n < mnEndCharPos) ) |
| break; |
| } |
| |
| // return zero if no more glyph found |
| if( nStart >= mnGlyphCount ) |
| return 0; |
| |
| // calculate absolute position in pixel units |
| Point aRelativePos = pG->maLinearPos - maBasePoint; |
| |
| // find more glyphs which can be merged into one drawing instruction |
| int nCount = 0; |
| long nYPos = pG->maLinearPos.Y(); |
| long nOldFlags = pG->maGlyphId; |
| for(;;) |
| { |
| // update return data with glyph info |
| ++nCount; |
| *(pGlyphs++) = pG->maGlyphId; |
| if( pCharPosAry ) |
| *(pCharPosAry++) = pG->mnCharPos; |
| if( pGlyphAdvAry ) |
| *pGlyphAdvAry = pG->mnNewWidth; |
| |
| // break at end of glyph list |
| if( ++nStart >= mnGlyphCount ) |
| break; |
| // break when enough glyphs |
| if( nCount >= nLen ) |
| break; |
| |
| long nGlyphAdvance = pG[1].maLinearPos.X() - pG->maLinearPos.X(); |
| if( pGlyphAdvAry ) |
| { |
| // override default advance width with correct value |
| *(pGlyphAdvAry++) = nGlyphAdvance; |
| } |
| else |
| { |
| // stop when next x-position is unexpected |
| if( pG->mnOrigWidth != nGlyphAdvance ) |
| break; |
| } |
| |
| // advance to next glyph |
| ++pG; |
| |
| // stop when next y-position is unexpected |
| if( nYPos != pG->maLinearPos.Y() ) |
| break; |
| |
| // stop when no longer in string |
| int n = pG->mnCharPos; |
| if( (n < mnMinCharPos) || (mnEndCharPos <= n) ) |
| break; |
| |
| // stop when glyph flags change |
| if( (nOldFlags ^ pG->maGlyphId) & GF_FLAGMASK ) |
| break; |
| |
| nOldFlags = pG->maGlyphId; // &GF_FLAGMASK not needed for test above |
| } |
| |
| aRelativePos.X() /= mnUnitsPerPixel; |
| aRelativePos.Y() /= mnUnitsPerPixel; |
| rPos = GetDrawPosition( aRelativePos ); |
| |
| return nCount; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::MoveGlyph( int nStart, long nNewXPos ) |
| { |
| if( nStart >= mnGlyphCount ) |
| return; |
| |
| GlyphItem* pG = mpGlyphItems + nStart; |
| // the nNewXPos argument determines the new cell position |
| // as RTL-glyphs are right justified in their cell |
| // the cell position needs to be adjusted to the glyph position |
| if( pG->IsRTLGlyph() ) |
| nNewXPos += pG->mnNewWidth - pG->mnOrigWidth; |
| // calculate the x-offset to the old position |
| long nXDelta = nNewXPos - pG->maLinearPos.X(); |
| // adjust all following glyph positions if needed |
| if( nXDelta != 0 ) |
| { |
| GlyphItem* const pGEnd = mpGlyphItems + mnGlyphCount; |
| for(; pG < pGEnd; ++pG ) |
| pG->maLinearPos.X() += nXDelta; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::DropGlyph( int nStart ) |
| { |
| if( nStart >= mnGlyphCount ) |
| return; |
| GlyphItem* pG = mpGlyphItems + nStart; |
| pG->maGlyphId = GF_DROPPED; |
| pG->mnCharPos = -1; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void GenericSalLayout::Simplify( bool bIsBase ) |
| { |
| const sal_GlyphId nDropMarker = bIsBase ? GF_DROPPED : 0; |
| |
| // remove dropped glyphs inplace |
| GlyphItem* pGDst = mpGlyphItems; |
| const GlyphItem* pGSrc = mpGlyphItems; |
| const GlyphItem* pGEnd = mpGlyphItems + mnGlyphCount; |
| for(; pGSrc < pGEnd; ++pGSrc ) |
| { |
| if( pGSrc->maGlyphId == nDropMarker ) |
| continue; |
| if( pGDst != pGSrc ) |
| *pGDst = *pGSrc; |
| ++pGDst; |
| } |
| mnGlyphCount = pGDst - mpGlyphItems; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| // make sure GlyphItems are sorted left to right |
| void GenericSalLayout::SortGlyphItems() |
| { |
| // move cluster components behind their cluster start (especially for RTL) |
| // using insertion sort because the glyph items are "almost sorted" |
| const GlyphItem* const pGEnd = mpGlyphItems + mnGlyphCount; |
| for( GlyphItem* pG = mpGlyphItems; pG < pGEnd; ++pG ) |
| { |
| // find a cluster starting with a diacritic |
| if( !pG->IsDiacritic() ) |
| continue; |
| if( !pG->IsClusterStart() ) |
| continue; |
| for( GlyphItem* pBaseGlyph = pG; ++pBaseGlyph < pGEnd; ) |
| { |
| // find the base glyph matching to the misplaced diacritic |
| if( pBaseGlyph->IsClusterStart() ) |
| break; |
| if( pBaseGlyph->IsDiacritic() ) |
| continue; |
| |
| // found the matching base glyph |
| // => this base glyph becomes the new cluster start |
| const GlyphItem aDiacritic = *pG; |
| *pG = *pBaseGlyph; |
| *pBaseGlyph = aDiacritic; |
| |
| // update glyph flags of swapped glyphitems |
| pG->mnFlags &= ~GlyphItem::IS_IN_CLUSTER; |
| pBaseGlyph->mnFlags |= GlyphItem::IS_IN_CLUSTER; |
| // prepare for checking next cluster |
| pG = pBaseGlyph; |
| break; |
| } |
| } |
| } |
| |
| // ======================================================================= |
| |
| MultiSalLayout::MultiSalLayout( SalLayout& rBaseLayout, const ImplFontData* pBaseFont ) |
| : SalLayout() |
| , mnLevel( 1 ) |
| , mbInComplete( false ) |
| { |
| //maFallbackRuns[0].Clear(); |
| mpFallbackFonts[ 0 ] = pBaseFont; |
| mpLayouts[ 0 ] = &rBaseLayout; |
| mnUnitsPerPixel = rBaseLayout.GetUnitsPerPixel(); |
| } |
| |
| void MultiSalLayout::SetInComplete(bool bInComplete) |
| { |
| mbInComplete = bInComplete; |
| maFallbackRuns[mnLevel-1] = ImplLayoutRuns(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| MultiSalLayout::~MultiSalLayout() |
| { |
| for( int i = 0; i < mnLevel; ++i ) |
| mpLayouts[ i ]->Release(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool MultiSalLayout::AddFallback( SalLayout& rFallback, |
| ImplLayoutRuns& rFallbackRuns, const ImplFontData* pFallbackFont ) |
| { |
| if( mnLevel >= MAX_FALLBACK ) |
| return false; |
| |
| mpFallbackFonts[ mnLevel ] = pFallbackFont; |
| mpLayouts[ mnLevel ] = &rFallback; |
| maFallbackRuns[ mnLevel-1 ] = rFallbackRuns; |
| ++mnLevel; |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool MultiSalLayout::LayoutText( ImplLayoutArgs& rArgs ) |
| { |
| if( mnLevel <= 1 ) |
| return false; |
| if (!mbInComplete) |
| maFallbackRuns[ mnLevel-1 ] = rArgs.maRuns; |
| return true; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void MultiSalLayout::AdjustLayout( ImplLayoutArgs& rArgs ) |
| { |
| SalLayout::AdjustLayout( rArgs ); |
| ImplLayoutArgs aMultiArgs = rArgs; |
| |
| if( !rArgs.mpDXArray && rArgs.mnLayoutWidth ) |
| { |
| // for stretched text in a MultiSalLayout the target width needs to be |
| // distributed by individually adjusting its virtual character widths |
| long nTargetWidth = aMultiArgs.mnLayoutWidth; |
| nTargetWidth *= mnUnitsPerPixel; // convert target width to base font units |
| aMultiArgs.mnLayoutWidth = 0; |
| |
| // we need to get the original unmodified layouts ready |
| for( int n = 0; n < mnLevel; ++n ) |
| mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs ); |
| // then we can measure the unmodified metrics |
| int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos; |
| sal_Int32* pJustificationArray = (sal_Int32*)alloca( nCharCount * sizeof(sal_Int32) ); |
| FillDXArray( pJustificationArray ); |
| // #i17359# multilayout is not simplified yet, so calculating the |
| // unjustified width needs handholding; also count the number of |
| // stretchable virtual char widths |
| long nOrigWidth = 0; |
| int nStretchable = 0; |
| for( int i = 0; i < nCharCount; ++i ) |
| { |
| // convert array from widths to sum of widths |
| nOrigWidth += pJustificationArray[i]; |
| if( pJustificationArray[i] > 0 ) |
| ++nStretchable; |
| } |
| |
| // now we are able to distribute the extra width over the virtual char widths |
| if( nOrigWidth && (nTargetWidth != nOrigWidth) ) |
| { |
| int nDiffWidth = nTargetWidth - nOrigWidth; |
| int nWidthSum = 0; |
| for( int i = 0; i < nCharCount; ++i ) |
| { |
| int nJustWidth = pJustificationArray[i]; |
| if( (nJustWidth > 0) && (nStretchable > 0) ) |
| { |
| int nDeltaWidth = nDiffWidth / nStretchable; |
| nJustWidth += nDeltaWidth; |
| nDiffWidth -= nDeltaWidth; |
| --nStretchable; |
| } |
| nWidthSum += nJustWidth; |
| pJustificationArray[i] = nWidthSum; |
| } |
| if( nWidthSum != nTargetWidth ) |
| pJustificationArray[ nCharCount-1 ] = nTargetWidth; |
| |
| // the justification array is still in base level units |
| // => convert it to pixel units |
| if( mnUnitsPerPixel > 1 ) |
| { |
| for( int i = 0; i < nCharCount; ++i ) |
| { |
| sal_Int32 nVal = pJustificationArray[ i ]; |
| nVal += (mnUnitsPerPixel + 1) / 2; |
| pJustificationArray[ i ] = nVal / mnUnitsPerPixel; |
| } |
| } |
| |
| // change the mpDXArray temporarilly (just for the justification) |
| aMultiArgs.mpDXArray = pJustificationArray; |
| } |
| } |
| |
| // Compute rtl flags, since in some scripts glyphs/char order can be |
| // reversed for a few character sequencies e.g. Myanmar |
| std::vector<bool> vRtl(rArgs.mnEndCharPos - rArgs.mnMinCharPos, false); |
| rArgs.ResetPos(); |
| bool bRtl; |
| int nRunStart, nRunEnd; |
| while (rArgs.GetNextRun(&nRunStart, &nRunEnd, &bRtl)) |
| { |
| if (bRtl) std::fill(vRtl.begin() + (nRunStart - rArgs.mnMinCharPos), |
| vRtl.begin() + (nRunEnd - rArgs.mnMinCharPos), true); |
| } |
| rArgs.ResetPos(); |
| |
| // prepare "merge sort" |
| int nStartOld[ MAX_FALLBACK ]; |
| int nStartNew[ MAX_FALLBACK ]; |
| int nCharPos[ MAX_FALLBACK ]; |
| sal_Int32 nGlyphAdv[ MAX_FALLBACK ]; |
| int nValid[ MAX_FALLBACK ] = {0}; |
| |
| sal_GlyphId nDummy; |
| Point aPos; |
| int nLevel = 0, n; |
| for( n = 0; n < mnLevel; ++n ) |
| { |
| // now adjust the individual components |
| if( n > 0 ) |
| { |
| aMultiArgs.maRuns = maFallbackRuns[ n-1 ]; |
| aMultiArgs.mnFlags |= SAL_LAYOUT_FOR_FALLBACK; |
| } |
| mpLayouts[n]->AdjustLayout( aMultiArgs ); |
| |
| // disable glyph-injection for glyph-fallback SalLayout iteration |
| mpLayouts[n]->DisableGlyphInjection( true ); |
| |
| // remove unused parts of component |
| if( n > 0 ) |
| { |
| if (mbInComplete && (n == mnLevel-1)) |
| mpLayouts[n]->Simplify( true ); |
| else |
| mpLayouts[n]->Simplify( false ); |
| } |
| |
| // prepare merging components |
| nStartNew[ nLevel ] = nStartOld[ nLevel ] = 0; |
| nValid[ nLevel ] = mpLayouts[n]->GetNextGlyphs( 1, &nDummy, aPos, |
| nStartNew[ nLevel ], &nGlyphAdv[ nLevel ], &nCharPos[ nLevel ] ); |
| #ifdef MULTI_SL_DEBUG |
| if (nValid[nLevel]) fprintf(mslLog(), "layout[%d]->GetNextGlyphs %d,%d x%d a%d c%d %x\n", n, nStartOld[nLevel], nStartNew[nLevel], aPos.X(), nGlyphAdv[nLevel], nCharPos[nLevel], |
| rArgs.mpStr[nCharPos[nLevel]]); |
| #endif |
| if( (n > 0) && !nValid[ nLevel ] ) |
| { |
| // an empty fallback layout can be released |
| mpLayouts[n]->Release(); |
| } |
| else |
| { |
| // reshuffle used fallbacks if needed |
| if( nLevel != n ) |
| { |
| mpLayouts[ nLevel ] = mpLayouts[ n ]; |
| mpFallbackFonts[ nLevel ] = mpFallbackFonts[ n ]; |
| maFallbackRuns[ nLevel ] = maFallbackRuns[ n ]; |
| } |
| ++nLevel; |
| } |
| } |
| mnLevel = nLevel; |
| |
| // prepare merge the fallback levels |
| long nXPos = 0; |
| double fUnitMul = 1.0; |
| for( n = 0; n < nLevel; ++n ) |
| maFallbackRuns[n].ResetPos(); |
| // get the next codepoint index that needs fallback |
| int nActiveCharPos = nCharPos[0]; |
| // get the end index of the active run |
| int nLastRunEndChar = (vRtl[nActiveCharPos - mnMinCharPos])? |
| rArgs.mnEndCharPos : rArgs.mnMinCharPos - 1; |
| int nRunVisibleEndChar = nCharPos[0]; |
| // merge the fallback levels |
| while( nValid[0] && (nLevel > 0)) |
| { |
| // find best fallback level |
| for( n = 0; n < nLevel; ++n ) |
| if( nValid[n] && !maFallbackRuns[n].PosIsInAnyRun( nActiveCharPos ) ) |
| // fallback level n wins when it requested no further fallback |
| break; |
| int nFBLevel = n; |
| |
| if( n < nLevel ) |
| { |
| // use base(n==0) or fallback(n>=1) level |
| fUnitMul = mnUnitsPerPixel; |
| fUnitMul /= mpLayouts[n]->GetUnitsPerPixel(); |
| long nNewPos = static_cast<long>(nXPos/fUnitMul + 0.5); |
| mpLayouts[n]->MoveGlyph( nStartOld[n], nNewPos ); |
| } |
| else |
| { |
| n = 0; // keep NotDef in base level |
| fUnitMul = 1.0; |
| } |
| |
| if( n > 0 ) |
| { |
| // drop the NotDef glyphs in the base layout run if a fallback run exists |
| while ( |
| (maFallbackRuns[ n-1 ].PosIsInRun( nCharPos[0] ) ) && |
| (!maFallbackRuns[ n ].PosIsInAnyRun( nCharPos[0] ) ) |
| ) |
| { |
| mpLayouts[0]->DropGlyph( nStartOld[0] ); |
| nStartOld[0] = nStartNew[0]; |
| nValid[0] = mpLayouts[0]->GetNextGlyphs( 1, &nDummy, aPos, |
| nStartNew[0], &nGlyphAdv[0], &nCharPos[0] ); |
| #ifdef MULTI_SL_DEBUG |
| if (nValid[0]) fprintf(mslLog(), "layout[0]->GetNextGlyphs %d,%d x%d a%d c%d %x\n", nStartOld[0], nStartNew[0], aPos.X(), nGlyphAdv[0], nCharPos[0], rArgs.mpStr[nCharPos[0]]); |
| #endif |
| if( !nValid[0] ) |
| break; |
| } |
| } |
| |
| // skip to end of layout run and calculate its advance width |
| int nRunAdvance = 0; |
| bool bKeepNotDef = (nFBLevel >= nLevel); |
| for(;;) |
| { |
| nRunAdvance += nGlyphAdv[n]; |
| |
| // proceed to next glyph |
| nStartOld[n] = nStartNew[n]; |
| int nOrigCharPos = nCharPos[n]; |
| nValid[n] = mpLayouts[n]->GetNextGlyphs( 1, &nDummy, aPos, |
| nStartNew[n], &nGlyphAdv[n], &nCharPos[n] ); |
| #ifdef MULTI_SL_DEBUG |
| if (nValid[n]) fprintf(mslLog(), "layout[%d]->GetNextGlyphs %d,%d a%d c%d %x\n", n, nStartOld[n], nStartNew[n], nGlyphAdv[n], nCharPos[n], rArgs.mpStr[nCharPos[n]]); |
| #endif |
| // break after last glyph of active layout |
| if( !nValid[n] ) |
| { |
| // performance optimization (when a fallback layout is no longer needed) |
| if( n >= nLevel-1 ) |
| --nLevel; |
| break; |
| } |
| |
| //If the next character is one which belongs to the next level, then we |
| //are finished here for now, and we'll pick up after the next level has |
| //been processed |
| if ((n+1 < nLevel) && (nCharPos[n] != nOrigCharPos)) |
| { |
| if (nOrigCharPos < nCharPos[n]) |
| { |
| if (nCharPos[n+1] > nOrigCharPos && (nCharPos[n+1] < nCharPos[n])) |
| break; |
| } |
| else if (nOrigCharPos > nCharPos[n]) |
| { |
| if (nCharPos[n+1] > nCharPos[n] && (nCharPos[n+1] < nOrigCharPos)) |
| break; |
| } |
| } |
| |
| // break at end of layout run |
| if( n > 0 ) |
| { |
| // skip until end of fallback run |
| if( !maFallbackRuns[n-1].PosIsInRun( nCharPos[n] ) ) |
| break; |
| } |
| else |
| { |
| // break when a fallback is needed and available |
| bool bNeedFallback = maFallbackRuns[0].PosIsInRun( nCharPos[0] ); |
| if( bNeedFallback ) |
| if( !maFallbackRuns[ nLevel-1 ].PosIsInRun( nCharPos[0] ) ) |
| break; |
| // break when change from resolved to unresolved base layout run |
| if( bKeepNotDef && !bNeedFallback ) |
| { maFallbackRuns[0].NextRun(); break; } |
| bKeepNotDef = bNeedFallback; |
| } |
| // check for reordered glyphs |
| if (aMultiArgs.mpDXArray && |
| nRunVisibleEndChar < mnEndCharPos && |
| nRunVisibleEndChar >= mnMinCharPos && |
| nCharPos[n] < mnEndCharPos && |
| nCharPos[n] >= mnMinCharPos) |
| { |
| if (vRtl[nActiveCharPos - mnMinCharPos]) |
| { |
| if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos] |
| >= aMultiArgs.mpDXArray[nCharPos[n] - mnMinCharPos]) |
| { |
| nRunVisibleEndChar = nCharPos[n]; |
| } |
| } |
| else if (aMultiArgs.mpDXArray[nRunVisibleEndChar-mnMinCharPos] |
| <= aMultiArgs.mpDXArray[nCharPos[n] - mnMinCharPos]) |
| { |
| nRunVisibleEndChar = nCharPos[n]; |
| } |
| } |
| } |
| |
| // if a justification array is available |
| // => use it directly to calculate the corresponding run width |
| if( aMultiArgs.mpDXArray ) |
| { |
| // the run advance is the width from the first char |
| // in the run to the first char in the next run |
| nRunAdvance = 0; |
| #ifdef MULTI_SL_DEBUG |
| const bool bLTR = !(vRtl[nActiveCharPos - mnMinCharPos]);//(nActiveCharPos < nCharPos[0]); |
| int nOldRunAdv = 0; |
| int nDXIndex = nCharPos[0] - mnMinCharPos - bLTR; |
| if( nDXIndex >= 0 ) |
| nOldRunAdv += aMultiArgs.mpDXArray[ nDXIndex ]; |
| nDXIndex = nActiveCharPos - mnMinCharPos - bLTR; |
| if( nDXIndex >= 0 ) |
| nOldRunAdv -= aMultiArgs.mpDXArray[ nDXIndex ]; |
| if( !bLTR ) |
| nOldRunAdv = -nOldRunAdv; |
| #endif |
| if (vRtl[nActiveCharPos - mnMinCharPos]) |
| { |
| if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos) |
| nRunAdvance -= aMultiArgs.mpDXArray[nRunVisibleEndChar - 1 - mnMinCharPos]; |
| if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos) |
| nRunAdvance += aMultiArgs.mpDXArray[nLastRunEndChar - 1 - mnMinCharPos]; |
| #ifdef MULTI_SL_DEBUG |
| fprintf(mslLog(), "rtl visible %d-%d,%d-%d adv%d(%d)\n", nLastRunEndChar-1, nRunVisibleEndChar-1, nActiveCharPos - bLTR, nCharPos[0] - bLTR, nRunAdvance, nOldRunAdv); |
| #endif |
| } |
| else |
| { |
| if (nRunVisibleEndChar >= mnMinCharPos) |
| nRunAdvance += aMultiArgs.mpDXArray[nRunVisibleEndChar - mnMinCharPos]; |
| if (nLastRunEndChar >= mnMinCharPos) |
| nRunAdvance -= aMultiArgs.mpDXArray[nLastRunEndChar - mnMinCharPos]; |
| #ifdef MULTI_SL_DEBUG |
| fprintf(mslLog(), "visible %d-%d,%d-%d adv%d(%d)\n", nLastRunEndChar, nRunVisibleEndChar, nActiveCharPos - bLTR, nCharPos[0] - bLTR, nRunAdvance, nOldRunAdv); |
| #endif |
| } |
| nLastRunEndChar = nRunVisibleEndChar; |
| nRunVisibleEndChar = nCharPos[0]; |
| // the requested width is still in pixel units |
| // => convert it to base level font units |
| nRunAdvance *= mnUnitsPerPixel; |
| } |
| else |
| { |
| // the measured width is still in fallback font units |
| // => convert it to base level font units |
| if( n > 0 ) // optimization: because (fUnitMul==1.0) for (n==0) |
| nRunAdvance = static_cast<long>(nRunAdvance*fUnitMul + 0.5); |
| } |
| |
| // calculate new x position (in base level units) |
| nXPos += nRunAdvance; |
| |
| // prepare for next fallback run |
| nActiveCharPos = nCharPos[0]; |
| // it essential that the runs don't get ahead of themselves and in the |
| // if( bKeepNotDef && !bNeedFallback ) statement above, the next run may |
| // have already been reached on the base level |
| for( int i = nFBLevel; --i >= 0;) |
| { |
| if (maFallbackRuns[i].GetRun(&nRunStart, &nRunEnd, &bRtl)) |
| { |
| if (bRtl) |
| { |
| if (nRunStart > nActiveCharPos) |
| maFallbackRuns[i].NextRun(); |
| } |
| else |
| { |
| if (nRunEnd <= nActiveCharPos) |
| maFallbackRuns[i].NextRun(); |
| } |
| } |
| } |
| } |
| |
| mpLayouts[0]->Simplify( true ); |
| |
| // reenable glyph-injection |
| for( n = 0; n < mnLevel; ++n ) |
| mpLayouts[n]->DisableGlyphInjection( false ); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void MultiSalLayout::InitFont() const |
| { |
| if( mnLevel > 0 ) |
| mpLayouts[0]->InitFont(); |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| const ImplFontData* MultiSalLayout::GetFallbackFontData( sal_GlyphId aGlyphId ) const |
| { |
| int nFallbackLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT; |
| return mpFallbackFonts[ nFallbackLevel ]; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void MultiSalLayout::DrawText( SalGraphics& rGraphics ) const |
| { |
| for( int i = mnLevel; --i >= 0; ) |
| { |
| SalLayout& rLayout = *mpLayouts[ i ]; |
| rLayout.DrawBase() += maDrawBase; |
| rLayout.DrawOffset() += maDrawOffset; |
| rLayout.InitFont(); |
| rLayout.DrawText( rGraphics ); |
| rLayout.DrawOffset() -= maDrawOffset; |
| rLayout.DrawBase() -= maDrawBase; |
| } |
| // NOTE: now the baselevel font is active again |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| int MultiSalLayout::GetTextBreak( long nMaxWidth, long nCharExtra, int nFactor ) const |
| { |
| if( mnLevel <= 0 ) |
| return STRING_LEN; |
| if( mnLevel == 1 ) |
| return mpLayouts[0]->GetTextBreak( nMaxWidth, nCharExtra, nFactor ); |
| |
| int nCharCount = mnEndCharPos - mnMinCharPos; |
| sal_Int32* pCharWidths = (sal_Int32*)alloca( 2*nCharCount * sizeof(sal_Int32) ); |
| mpLayouts[0]->FillDXArray( pCharWidths ); |
| |
| for( int n = 1; n < mnLevel; ++n ) |
| { |
| SalLayout& rLayout = *mpLayouts[ n ]; |
| rLayout.FillDXArray( pCharWidths + nCharCount ); |
| double fUnitMul = mnUnitsPerPixel; |
| fUnitMul /= rLayout.GetUnitsPerPixel(); |
| for( int i = 0; i < nCharCount; ++i ) |
| { |
| long w = pCharWidths[ i + nCharCount ]; |
| w = static_cast<long>(w*fUnitMul + 0.5); |
| pCharWidths[ i ] += w; |
| } |
| } |
| |
| long nWidth = 0; |
| for( int i = 0; i < nCharCount; ++i ) |
| { |
| nWidth += pCharWidths[ i ] * nFactor; |
| if( nWidth > nMaxWidth ) |
| return (i + mnMinCharPos); |
| nWidth += nCharExtra; |
| } |
| |
| return STRING_LEN; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| long MultiSalLayout::FillDXArray( sal_Int32* pCharWidths ) const |
| { |
| long nMaxWidth = 0; |
| |
| // prepare merging of fallback levels |
| sal_Int32* pTempWidths = NULL; |
| const int nCharCount = mnEndCharPos - mnMinCharPos; |
| if( pCharWidths ) |
| { |
| for( int i = 0; i < nCharCount; ++i ) |
| pCharWidths[i] = 0; |
| pTempWidths = (sal_Int32*)alloca( nCharCount * sizeof(sal_Int32) ); |
| } |
| |
| for( int n = mnLevel; --n >= 0; ) |
| { |
| // query every fallback level |
| long nTextWidth = mpLayouts[n]->FillDXArray( pTempWidths ); |
| if( !nTextWidth ) |
| continue; |
| // merge results from current level |
| double fUnitMul = mnUnitsPerPixel; |
| fUnitMul /= mpLayouts[n]->GetUnitsPerPixel(); |
| nTextWidth = static_cast<long>(nTextWidth * fUnitMul + 0.5); |
| if( nMaxWidth < nTextWidth ) |
| nMaxWidth = nTextWidth; |
| if( !pCharWidths ) |
| continue; |
| // calculate virtual char widths using most probable fallback layout |
| for( int i = 0; i < nCharCount; ++i ) |
| { |
| // #i17359# restriction: |
| // one char cannot be resolved from different fallbacks |
| if( pCharWidths[i] != 0 ) |
| continue; |
| long nCharWidth = pTempWidths[i]; |
| if( !nCharWidth ) |
| continue; |
| nCharWidth = static_cast<long>(nCharWidth * fUnitMul + 0.5); |
| pCharWidths[i] = nCharWidth; |
| } |
| } |
| |
| return nMaxWidth; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| void MultiSalLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const |
| { |
| SalLayout& rLayout = *mpLayouts[ 0 ]; |
| rLayout.GetCaretPositions( nMaxIndex, pCaretXArray ); |
| |
| if( mnLevel > 1 ) |
| { |
| sal_Int32* pTempPos = (sal_Int32*)alloca( nMaxIndex * sizeof(sal_Int32) ); |
| for( int n = 1; n < mnLevel; ++n ) |
| { |
| mpLayouts[ n ]->GetCaretPositions( nMaxIndex, pTempPos ); |
| double fUnitMul = mnUnitsPerPixel; |
| fUnitMul /= mpLayouts[n]->GetUnitsPerPixel(); |
| for( int i = 0; i < nMaxIndex; ++i ) |
| if( pTempPos[i] >= 0 ) |
| { |
| long w = pTempPos[i]; |
| w = static_cast<long>(w*fUnitMul + 0.5); |
| pCaretXArray[i] = w; |
| } |
| } |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| int MultiSalLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphIdxAry, Point& rPos, |
| int& nStart, sal_Int32* pGlyphAdvAry, int* pCharPosAry ) const |
| { |
| // for multi-level fallback only single glyphs should be used |
| if( mnLevel > 1 && nLen > 1 ) |
| nLen = 1; |
| |
| // NOTE: nStart is tagged with current font index |
| int nLevel = static_cast<unsigned>(nStart) >> GF_FONTSHIFT; |
| nStart &= ~GF_FONTMASK; |
| for(; nLevel < mnLevel; ++nLevel, nStart=0 ) |
| { |
| SalLayout& rLayout = *mpLayouts[ nLevel ]; |
| rLayout.InitFont(); |
| int nRetVal = rLayout.GetNextGlyphs( nLen, pGlyphIdxAry, rPos, |
| nStart, pGlyphAdvAry, pCharPosAry ); |
| if( nRetVal ) |
| { |
| int nFontTag = nLevel << GF_FONTSHIFT; |
| nStart |= nFontTag; |
| double fUnitMul = mnUnitsPerPixel; |
| fUnitMul /= mpLayouts[nLevel]->GetUnitsPerPixel(); |
| for( int i = 0; i < nRetVal; ++i ) |
| { |
| if( pGlyphAdvAry ) |
| { |
| long w = pGlyphAdvAry[i]; |
| w = static_cast<long>(w * fUnitMul + 0.5); |
| pGlyphAdvAry[i] = w; |
| } |
| pGlyphIdxAry[ i ] |= nFontTag; |
| } |
| rPos += maDrawBase; |
| rPos += maDrawOffset; |
| return nRetVal; |
| } |
| } |
| |
| // #111016# reset to base level font when done |
| mpLayouts[0]->InitFont(); |
| return 0; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool MultiSalLayout::GetOutline( SalGraphics& rGraphics, |
| ::basegfx::B2DPolyPolygonVector& rPPV ) const |
| { |
| bool bRet = false; |
| |
| for( int i = mnLevel; --i >= 0; ) |
| { |
| SalLayout& rLayout = *mpLayouts[ i ]; |
| rLayout.DrawBase() = maDrawBase; |
| rLayout.DrawOffset() += maDrawOffset; |
| rLayout.InitFont(); |
| bRet |= rLayout.GetOutline( rGraphics, rPPV ); |
| rLayout.DrawOffset() -= maDrawOffset; |
| } |
| |
| return bRet; |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| bool MultiSalLayout::GetBoundRect( SalGraphics& rGraphics, Rectangle& rRect ) const |
| { |
| bool bRet = false; |
| |
| Rectangle aRectangle; |
| for( int i = mnLevel; --i >= 0; ) |
| { |
| SalLayout& rLayout = *mpLayouts[ i ]; |
| rLayout.DrawBase() = maDrawBase; |
| rLayout.DrawOffset() += maDrawOffset; |
| rLayout.InitFont(); |
| if( rLayout.GetBoundRect( rGraphics, aRectangle ) ) |
| { |
| rRect.Union( aRectangle ); |
| bRet = true; |
| } |
| rLayout.DrawOffset() -= maDrawOffset; |
| } |
| |
| return bRet; |
| } |
| |
| // ======================================================================= |