summaryrefslogtreecommitdiff
path: root/vcl/win/gdi/winlayout.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/win/gdi/winlayout.cxx')
-rw-r--r--vcl/win/gdi/winlayout.cxx2947
1 files changed, 2947 insertions, 0 deletions
diff --git a/vcl/win/gdi/winlayout.cxx b/vcl/win/gdi/winlayout.cxx
new file mode 100644
index 000000000000..b5ce57e51bfb
--- /dev/null
+++ b/vcl/win/gdi/winlayout.cxx
@@ -0,0 +1,2947 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * 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 .
+ */
+
+#include "winlayout.hxx"
+
+#include "osl/module.h"
+#include "osl/file.h"
+
+#include <comphelper/windowserrorstring.hxx>
+
+#include <opengl/texture.hxx>
+#include <opengl/win/gdiimpl.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <outdev.h>
+
+#include "sft.hxx"
+#include "sallayout.hxx"
+#include "glyphy/demo.hxx"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include <sal/alloca.h>
+
+#include <algorithm>
+
+#include <shlwapi.h>
+#include <winver.h>
+
+#include <unordered_map>
+
+// Graphite headers
+#include <config_graphite.h>
+#if ENABLE_GRAPHITE
+#include <i18nlangtag/languagetag.hxx>
+#include <graphite_features.hxx>
+#endif
+
+#define DROPPED_OUTGLYPH 0xFFFF
+
+namespace
+{
+// Extra space at the top and bottom of the glyph in total = tmHeight / GLYPH_SPACE_RATIO;
+const int GLYPH_SPACE_RATIO = 8;
+// Border size at the top of the glyph = tmHeight / GLYPH_OFFSET_RATIO;
+const int GLYPH_OFFSET_RATIO = GLYPH_SPACE_RATIO * 2;
+}
+
+struct OpenGLGlyphCacheChunk
+{
+ WORD mnFirstGlyph;
+ int mnGlyphCount;
+ std::vector<Rectangle> maLocation;
+ std::shared_ptr<OpenGLTexture> mpTexture;
+ int mnAscent;
+ int mnHeight;
+ bool mbVertical;
+
+ int getExtraSpace() const
+ {
+ return std::max(mnHeight / GLYPH_SPACE_RATIO, 4);
+ }
+
+ int getExtraOffset() const
+ {
+ return std::max(mnHeight / GLYPH_OFFSET_RATIO, 2);
+ }
+};
+
+// win32 specific physical font instance
+class ImplWinFontEntry : public ImplFontEntry
+{
+public:
+ explicit ImplWinFontEntry( FontSelectPattern& );
+ virtual ~ImplWinFontEntry();
+ void setupGLyphy(HDC hDC);
+
+private:
+ // TODO: also add HFONT??? Watch out for issues with too many active fonts...
+
+public:
+ SCRIPT_CACHE& GetScriptCache() const
+ { return maScriptCache; }
+private:
+ mutable SCRIPT_CACHE maScriptCache;
+ std::vector<OpenGLGlyphCacheChunk> maOpenGLGlyphCache;
+
+public:
+ bool InitKashidaHandling( HDC );
+ int GetMinKashidaWidth() const { return mnMinKashidaWidth; }
+ int GetMinKashidaGlyph() const { return mnMinKashidaGlyph; }
+
+ static GLuint mnGLyphyProgram;
+ demo_atlas_t* mpGLyphyAtlas;
+ demo_font_t* mpGLyphyFont;
+
+ bool GlyphIsCached(int nGlyphIndex) const;
+ bool AddChunkOfGlyphs(int nGlyphIndex, const WinLayout& rLayout, SalGraphics& rGraphics);
+ const OpenGLGlyphCacheChunk& GetCachedGlyphChunkFor(int nGlyphIndex) const;
+
+private:
+ mutable int mnMinKashidaWidth;
+ mutable int mnMinKashidaGlyph;
+ bool mbGLyphySetupCalled;
+};
+
+GLuint ImplWinFontEntry::mnGLyphyProgram = 0;
+
+#ifdef SAL_LOG_INFO
+
+namespace {
+
+char ColorFor(COLORREF aColor)
+{
+ if (aColor == RGB(0xFF, 0xFF, 0xFF))
+ return ' ';
+ else if (aColor == RGB(0x00, 0x00, 0x00))
+ return 'X';
+
+ return '0' + (10*(GetRValue(aColor) + GetGValue(aColor) + GetBValue(aColor))) / (0xFF*3);
+}
+
+void DumpGlyphBitmap(HDC hDC)
+{
+ HBITMAP hBitmap = static_cast<HBITMAP>(GetCurrentObject(hDC, OBJ_BITMAP));
+ if (hBitmap == NULL)
+ {
+ SAL_WARN("vcl.gdi", "GetCurrentObject failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+
+ BITMAP aBitmap;
+ if (!GetObjectW(hBitmap, sizeof(aBitmap), &aBitmap))
+ {
+ SAL_WARN("vcl.gdi", "GetObjectW failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+
+ SAL_INFO("vcl.gdi.opengl", "Bitmap " << hBitmap << ": " << aBitmap.bmWidth << "x" << aBitmap.bmHeight << ":");
+
+ std::ostringstream sLine("\n");
+ for (long y = 0; y < aBitmap.bmHeight; y++)
+ {
+ for (long x = 0; x < std::min(75l, aBitmap.bmWidth); x++)
+ sLine << ColorFor(GetPixel(hDC, x, y));
+ if (y < aBitmap.bmHeight - 1)
+ sLine << "\n";
+ }
+ SAL_INFO("vcl.gdi.opengl", sLine.str());
+}
+
+} // anonymous namespace
+
+#endif // SAL_LOG_INFO
+
+template< typename charT, typename traits >
+inline std::basic_ostream<charT, traits> & operator <<(
+ std::basic_ostream<charT, traits> & stream, const std::vector<OpenGLGlyphCacheChunk>& rCache )
+{
+ stream << "{";
+ for (auto i = rCache.cbegin(); i != rCache.cend(); ++i)
+ {
+ stream << "[" << i->mnFirstGlyph;
+ if (i->mnGlyphCount > 1)
+ stream << ".." << (i->mnFirstGlyph + i->mnGlyphCount - 1);
+ stream << "]";
+ if (i+1 != rCache.cend())
+ {
+ stream << ",";
+ assert(i->mnFirstGlyph + i->mnGlyphCount <= (i+1)->mnFirstGlyph);
+ }
+ }
+
+ return stream << "}";
+}
+
+bool ImplWinFontEntry::GlyphIsCached(int nGlyphIndex) const
+{
+ if (nGlyphIndex == DROPPED_OUTGLYPH)
+ return true;
+
+ for (size_t i = 0; i < maOpenGLGlyphCache.size(); i++)
+ if (nGlyphIndex >= maOpenGLGlyphCache[i].mnFirstGlyph &&
+ nGlyphIndex < maOpenGLGlyphCache[i].mnFirstGlyph + maOpenGLGlyphCache[i].mnGlyphCount)
+ return true;
+
+ return false;
+}
+
+bool ImplWinFontEntry::AddChunkOfGlyphs(int nGlyphIndex, const WinLayout& rLayout, SalGraphics& rGraphics)
+{
+ const int DEFAULT_CHUNK_SIZE = 20;
+
+ if (nGlyphIndex == DROPPED_OUTGLYPH)
+ return true;
+
+ SAL_INFO("vcl.gdi.opengl", "this=" << this << " " << nGlyphIndex << " old: " << maOpenGLGlyphCache);
+
+ auto n = maOpenGLGlyphCache.begin();
+ while (n != maOpenGLGlyphCache.end() &&
+ nGlyphIndex > n->mnFirstGlyph)
+ ++n;
+ assert(n == maOpenGLGlyphCache.end() || nGlyphIndex < n->mnFirstGlyph);
+
+ int nCount = DEFAULT_CHUNK_SIZE;
+ if (n != maOpenGLGlyphCache.end() && nGlyphIndex + nCount >= n->mnFirstGlyph)
+ nCount = n->mnFirstGlyph - nGlyphIndex;
+
+ if (nCount < DEFAULT_CHUNK_SIZE)
+ {
+ if (n == maOpenGLGlyphCache.begin())
+ {
+ nGlyphIndex = std::max(0, n->mnFirstGlyph - DEFAULT_CHUNK_SIZE);
+ }
+ else
+ {
+ nGlyphIndex = std::max(n[-1].mnFirstGlyph + n[-1].mnGlyphCount,
+ n->mnFirstGlyph - DEFAULT_CHUNK_SIZE);
+ }
+ nCount = n->mnFirstGlyph - nGlyphIndex;
+ }
+
+ OpenGLGlyphCacheChunk aChunk;
+ aChunk.mnFirstGlyph = nGlyphIndex;
+ aChunk.mnGlyphCount = nCount;
+
+ std::vector<WORD> aGlyphIndices(nCount);
+ for (int i = 0; i < nCount; i++)
+ aGlyphIndices[i] = nGlyphIndex + i;
+
+ HDC hDC = CreateCompatibleDC(rLayout.mhDC);
+ if (hDC == NULL)
+ {
+ SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+ HFONT hOrigFont = static_cast<HFONT>(SelectObject(hDC, rLayout.mhFont));
+ if (hOrigFont == NULL)
+ {
+ SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
+ DeleteDC(hDC);
+ return false;
+ }
+
+ SIZE aSize;
+
+ if (!GetTextExtentExPointI(hDC, aGlyphIndices.data(), nCount, 0, NULL, NULL, &aSize))
+ {
+ SAL_WARN("vcl.gdi", "GetTextExtentExPointI failed: " << WindowsErrorString(GetLastError()));
+ SelectObject(hDC, hOrigFont);
+ DeleteDC(hDC);
+ return false;
+ }
+
+ std::vector<ABC> aABC(nCount);
+ if (!GetCharABCWidthsI(hDC, 0, nCount, aGlyphIndices.data(), aABC.data()))
+ {
+ SAL_WARN("vcl.gdi", "GetCharABCWidthsI failed: " << WindowsErrorString(GetLastError()));
+ SelectObject(hDC, hOrigFont);
+ DeleteDC(hDC);
+ return false;
+ }
+
+ std::ostringstream sLine;
+ for (int i = 0; i < nCount; i++)
+ sLine << aABC[i].abcA << ":" << aABC[i].abcB << ":" << aABC[i].abcC << " ";
+ SAL_INFO("vcl.gdi.opengl", "ABC widths: " << sLine.str());
+
+ TEXTMETRICW aTextMetric;
+ if (!GetTextMetricsW(hDC, &aTextMetric))
+ {
+ SAL_WARN("vcl.gdi", "GetTextMetrics failed: " << WindowsErrorString(GetLastError()));
+ SelectObject(hDC, hOrigFont);
+ DeleteDC(hDC);
+ return false;
+ }
+ aChunk.mnAscent = aTextMetric.tmAscent;
+ aChunk.mnHeight = aTextMetric.tmHeight;
+
+ // Try hard to avoid overlap as we want to be able to use
+ // individual rectangles for each glyph. The ABC widths don't
+ // take anti-aliasing into consideration. Let's hope that leaving
+ // "extra" space inbetween glyphs will help.
+ std::vector<int> aDX(nCount);
+ int totWidth = 0;
+ for (int i = 0; i < nCount; i++)
+ {
+ aDX[i] = aABC[i].abcB + std::abs(aABC[i].abcC);
+ if (i == 0)
+ aDX[0] += std::abs(aABC[0].abcA);
+ if (i < nCount-1)
+ aDX[i] += std::abs(aABC[i+1].abcA);
+ aDX[i] += aChunk.getExtraSpace();
+ totWidth += aDX[i];
+ }
+
+ LOGFONTW aLogfont;
+ if (!GetObjectW(rLayout.mhFont, sizeof(aLogfont), &aLogfont))
+ {
+ SAL_WARN("vcl.gdi", "GetObject failed: " << WindowsErrorString(GetLastError()));
+ SelectObject(hDC, hOrigFont);
+ DeleteDC(hDC);
+ return false;
+ }
+
+ wchar_t sFaceName[200];
+ int nFaceNameLen = GetTextFaceW(hDC, SAL_N_ELEMENTS(sFaceName), sFaceName);
+ if (!nFaceNameLen)
+ {
+ SAL_WARN("vcl.gdi", "GetTextFace failed: " << WindowsErrorString(GetLastError()));
+ SelectObject(hDC, hOrigFont);
+ DeleteDC(hDC);
+ return false;
+ }
+
+ SAL_INFO("vcl.gdi.opengl", OUString(sFaceName, nFaceNameLen) <<
+ ": Escapement=" << aLogfont.lfEscapement <<
+ " Orientation=" << aLogfont.lfOrientation <<
+ " Ascent=" << aTextMetric.tmAscent <<
+ " InternalLeading=" << aTextMetric.tmInternalLeading <<
+ " Size=(" << aSize.cx << "," << aSize.cy << ") totWidth=" << totWidth);
+
+ if (SelectObject(hDC, hOrigFont) == NULL)
+ SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
+ if (!DeleteDC(hDC))
+ SAL_WARN("vcl.gdi", "DeleteDC failed: " << WindowsErrorString(GetLastError()));
+
+ // Leave extra space also at top and bottom
+ int nBitmapWidth, nBitmapHeight;
+ if (sFaceName[0] == '@')
+ {
+ nBitmapWidth = aSize.cy + aChunk.getExtraSpace();
+ nBitmapHeight = totWidth;
+ aChunk.mbVertical = true;
+ }
+ else
+ {
+ nBitmapWidth = totWidth;
+ nBitmapHeight = aSize.cy + aChunk.getExtraSpace();
+ aChunk.mbVertical = false;
+ }
+
+ if (aChunk.mbVertical && aLogfont.lfEscapement != 2700)
+ return false;
+
+ OpenGLCompatibleDC aDC(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight);
+
+ HFONT hNonAntialiasedFont = NULL;
+
+#ifdef DBG_UTIL
+ static bool bNoAntialias = (std::getenv("VCL_GLYPH_CACHING_HACK_NO_ANTIALIAS") != NULL);
+ if (bNoAntialias)
+ {
+ aLogfont.lfQuality = NONANTIALIASED_QUALITY;
+ hNonAntialiasedFont = CreateFontIndirectW(&aLogfont);
+ if (hNonAntialiasedFont == NULL)
+ {
+ SAL_WARN("vcl.gdi", "CreateFontIndirect failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+ }
+#endif
+
+ hOrigFont = SelectFont(aDC.getCompatibleHDC(), hNonAntialiasedFont != NULL ? hNonAntialiasedFont : rLayout.mhFont);
+ if (hOrigFont == NULL)
+ {
+ SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ SetTextColor(aDC.getCompatibleHDC(), RGB(0, 0, 0));
+ SetBkColor(aDC.getCompatibleHDC(), RGB(255, 255, 255));
+
+ aDC.fill(MAKE_SALCOLOR(0xff, 0xff, 0xff));
+
+ int nY = aChunk.getExtraOffset();
+ int nX = nY;
+ if (aChunk.mbVertical)
+ nX += aDX[0];
+ if (!ExtTextOutW(aDC.getCompatibleHDC(), nX, nY, ETO_GLYPH_INDEX, NULL, reinterpret_cast<wchar_t *>(aGlyphIndices.data()), nCount, aDX.data()))
+ {
+ SAL_WARN("vcl.gdi", "ExtTextOutW failed: " << WindowsErrorString(GetLastError()));
+ SelectFont(aDC.getCompatibleHDC(), hOrigFont);
+ if (hNonAntialiasedFont != NULL)
+ DeleteObject(hNonAntialiasedFont);
+ return false;
+ }
+
+ aChunk.maLocation.resize(nCount);
+ UINT nPos = 0;
+ for (int i = 0; i < nCount; i++)
+ {
+ if (aChunk.mbVertical)
+ {
+ aChunk.maLocation[i].Left() = 0;
+ aChunk.maLocation[i].Right() = nBitmapWidth;
+ aChunk.maLocation[i].Top() = nPos;
+ aChunk.maLocation[i].Bottom() = nPos + aDX[i];
+ nPos = aChunk.maLocation[i].Bottom();
+ }
+ else
+ {
+ aChunk.maLocation[i].Left() = nPos;
+ aChunk.maLocation[i].Right() = nPos + aDX[i];
+ nPos = aChunk.maLocation[i].Right();
+ aChunk.maLocation[i].Top() = 0;
+ aChunk.maLocation[i].Bottom() = aSize.cy + aChunk.getExtraSpace();
+ }
+ }
+
+ aChunk.mpTexture = std::unique_ptr<OpenGLTexture>(aDC.getTexture());
+
+ maOpenGLGlyphCache.insert(n, aChunk);
+
+ SelectFont(aDC.getCompatibleHDC(), hOrigFont);
+ if (hNonAntialiasedFont != NULL)
+ DeleteObject(hNonAntialiasedFont);
+
+#ifdef SAL_LOG_INFO
+ SAL_INFO("vcl.gdi.opengl", "this=" << this << " now: " << maOpenGLGlyphCache);
+ DumpGlyphBitmap(aDC.getCompatibleHDC());
+#endif
+
+ return true;
+}
+
+const OpenGLGlyphCacheChunk& ImplWinFontEntry::GetCachedGlyphChunkFor(int nGlyphIndex) const
+{
+ auto i = maOpenGLGlyphCache.cbegin();
+ while (i != maOpenGLGlyphCache.cend() && nGlyphIndex >= i->mnFirstGlyph + i->mnGlyphCount)
+ ++i;
+ assert(i != maOpenGLGlyphCache.cend());
+ assert(nGlyphIndex >= i->mnFirstGlyph && nGlyphIndex < i->mnFirstGlyph + i->mnGlyphCount);
+ return *i;
+}
+
+void ImplWinFontEntry::setupGLyphy(HDC hDC)
+{
+ if (mbGLyphySetupCalled)
+ return;
+
+ mbGLyphySetupCalled = true;
+
+ // First get the OUTLINETEXTMETRIC to find the font's em unit. Then, to get an unmodified (not
+ // grid-fitted) glyph outline, create the font anew at that size. That is as the doc for
+ // GetGlyphOutline() suggests.
+ OUTLINETEXTMETRICW aOutlineTextMetric;
+ if (!GetOutlineTextMetricsW (hDC, sizeof (OUTLINETEXTMETRICW), &aOutlineTextMetric))
+ {
+ SAL_WARN("vcl.gdi.opengl", "GetOutlineTextMetricsW failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+
+ HFONT hFont = (HFONT)GetCurrentObject(hDC, OBJ_FONT);
+ LOGFONTW aLogFont;
+ GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont);
+
+ HDC hNewDC = GetDC(NULL);
+ if (hNewDC == NULL)
+ {
+ SAL_WARN("vcl.gdi.opengl", "GetDC failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+
+ hNewDC = CreateCompatibleDC(hNewDC);
+ if (hNewDC == NULL)
+ {
+ SAL_WARN("vcl.gdi.opengl", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+
+ aLogFont.lfHeight = aOutlineTextMetric.otmEMSquare;
+ hFont = CreateFontIndirectW(&aLogFont);
+ if (hFont == NULL)
+ {
+ SAL_WARN("vcl.gdi.opengl", "CreateFontIndirectW failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+ if (SelectObject(hNewDC, hFont) == NULL)
+ {
+ SAL_WARN("vcl.gdi.opengl", "SelectObject failed: " << WindowsErrorString(GetLastError()));
+ return;
+ }
+
+ if (mnGLyphyProgram == 0)
+ mnGLyphyProgram = demo_shader_create_program();
+
+ mpGLyphyAtlas = demo_atlas_create(2048, 1024, 64, 8);
+ mpGLyphyFont = demo_font_create(hNewDC, mpGLyphyAtlas);
+}
+
+WinLayout::WinLayout(HDC hDC, const ImplWinFontData& rWFD, ImplWinFontEntry& rWFE, bool bUseOpenGL)
+: mhDC( hDC ),
+ mhFont( (HFONT)GetCurrentObject(hDC,OBJ_FONT) ),
+ mnBaseAdv( 0 ),
+ mfFontScale( 1.0 ),
+ mbUseOpenGL(bUseOpenGL),
+ mrWinFontData( rWFD ),
+ mrWinFontEntry(rWFE)
+{
+ // keep mrWinFontEntry alive
+ mrWinFontEntry.m_pFontCache->Acquire(&mrWinFontEntry);
+}
+
+WinLayout::~WinLayout()
+{
+ mrWinFontEntry.m_pFontCache->Release(&mrWinFontEntry);
+}
+
+void WinLayout::InitFont() const
+{
+ SelectObject( mhDC, mhFont );
+}
+
+// Using reasonably sized fonts to emulate huge fonts works around
+// a lot of problems in printer and display drivers. Huge fonts are
+// mostly used by high resolution reference devices which are never
+// painted to anyway. In the rare case that a huge font needs to be
+// displayed somewhere then the workaround doesn't help anymore.
+// If the drivers fail silently for huge fonts, so be it...
+HFONT WinLayout::DisableFontScaling() const
+{
+ if( mfFontScale == 1.0 )
+ return 0;
+
+ LOGFONTW aLogFont;
+ GetObjectW( mhFont, sizeof(LOGFONTW), &aLogFont);
+ aLogFont.lfHeight = (LONG)(mfFontScale * aLogFont.lfHeight);
+ aLogFont.lfWidth = (LONG)(mfFontScale * aLogFont.lfWidth);
+ HFONT hHugeFont = CreateFontIndirectW( &aLogFont);
+ if( !hHugeFont )
+ return 0;
+
+ return SelectFont( mhDC, hHugeFont );
+}
+
+SCRIPT_CACHE& WinLayout::GetScriptCache() const
+{
+ return mrWinFontEntry.GetScriptCache();
+}
+
+void WinLayout::DrawText(SalGraphics& rGraphics) const
+{
+ WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics);
+ HDC hDC = rWinGraphics.getHDC();
+
+ if (!mbUseOpenGL)
+ {
+ // no OpenGL, just classic rendering
+ DrawTextImpl(hDC);
+ }
+ else if (CacheGlyphs(rGraphics) &&
+ DrawCachedGlyphs(rGraphics))
+ {
+ // Nothing
+ }
+ else
+ {
+ // We have to render the text to a hidden texture, and draw it.
+ //
+ // Note that Windows GDI does not really support the alpha correctly
+ // when drawing - ie. it draws nothing to the alpha channel when
+ // rendering the text, even the antialiasing is done as 'real' pixels,
+ // not alpha...
+ //
+ // Luckily, this does not really limit us:
+ //
+ // To blend properly, we draw the texture, but then use it as an alpha
+ // channel for solid color (that will define the text color). This
+ // destroys the subpixel antialiasing - turns it into 'classic'
+ // antialiasing - but that is the best we can do, because the subpixel
+ // antialiasing needs to know what is in the background: When the
+ // background is white, or white-ish, it does the subpixel, but when
+ // there is a color, it just darkens the color (and does this even
+ // when part of the character is on a colored background, and part on
+ // white). It has to work this way, the results would look strange
+ // otherwise.
+ //
+ // For the GL rendering to work even with the subpixel antialiasing,
+ // we would need to get the current texture from the screen, let GDI
+ // draw the text to it (so that it can decide well where to use the
+ // subpixel and where not), and draw the result - but in that case we
+ // don't need alpha anyway.
+ //
+ // TODO: check the performance of this 2nd approach at some stage and
+ // switch to that if it performs well.
+
+ Rectangle aRect;
+ GetBoundRect(rGraphics, aRect);
+
+ OpenGLCompatibleDC aDC(rGraphics, aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
+
+ // we are making changes to the DC, make sure we got a new one
+ assert(aDC.getCompatibleHDC() != hDC);
+
+ // setup the hidden DC with black color and white background, we will
+ // use the result of the text drawing later as a mask only
+ HFONT hOrigFont = SelectFont(aDC.getCompatibleHDC(), mhFont);
+
+ SetTextColor(aDC.getCompatibleHDC(), RGB(0, 0, 0));
+ SetBkColor(aDC.getCompatibleHDC(), RGB(255, 255, 255));
+
+ UINT nTextAlign = GetTextAlign(hDC);
+ SetTextAlign(aDC.getCompatibleHDC(), nTextAlign);
+
+ // the actual drawing
+ DrawTextImpl(aDC.getCompatibleHDC());
+
+ COLORREF color = GetTextColor(hDC);
+ SalColor salColor = MAKE_SALCOLOR(GetRValue(color), GetGValue(color), GetBValue(color));
+
+ WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(rWinGraphics.mpImpl.get());
+ if (pImpl)
+ {
+ pImpl->PreDraw();
+
+ std::unique_ptr<OpenGLTexture> xTexture(aDC.getTexture());
+ if (xTexture)
+ pImpl->DrawMask(*xTexture, salColor, aDC.getTwoRect());
+
+ pImpl->PostDraw();
+ }
+
+ SelectFont(aDC.getCompatibleHDC(), hOrigFont);
+ }
+}
+
+struct VisualItem
+{
+public:
+ SCRIPT_ITEM* mpScriptItem;
+ int mnMinGlyphPos;
+ int mnEndGlyphPos;
+ int mnMinCharPos;
+ int mnEndCharPos;
+ int mnXOffset;
+ ABC maABCWidths;
+ bool mbHasKashidas;
+
+public:
+ bool IsEmpty() const { return (mnEndGlyphPos <= 0); }
+ bool IsRTL() const { return mpScriptItem->a.fRTL; }
+ bool HasKashidas() const { return mbHasKashidas; }
+};
+
+static bool bUspInited = false;
+
+static bool bManualCellAlign = true;
+
+static void InitUSP()
+{
+#if _WIN32_WINNT < _WIN32_WINNT_VISTA
+ // get the usp10.dll version info
+ HMODULE usp10 = GetModuleHandle("usp10.dll");
+ void *pScriptIsComplex = reinterpret_cast< void* >( GetProcAddress(usp10, "ScriptIsComplex"));
+ int nUspVersion = 0;
+ rtl_uString* pModuleURL = NULL;
+ osl_getModuleURLFromAddress( pScriptIsComplex, &pModuleURL );
+ rtl_uString* pModuleFileName = NULL;
+ if( pModuleURL )
+ osl_getSystemPathFromFileURL( pModuleURL, &pModuleFileName );
+ const sal_Unicode* pModuleFileCStr = NULL;
+ if( pModuleFileName )
+ pModuleFileCStr = rtl_uString_getStr( pModuleFileName );
+ if( pModuleFileCStr )
+ {
+ DWORD nHandle;
+ DWORD nBufSize = GetFileVersionInfoSizeW( const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(pModuleFileCStr)), &nHandle );
+ char* pBuffer = (char*)alloca( nBufSize );
+ BOOL bRC = GetFileVersionInfoW( const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(pModuleFileCStr)), nHandle, nBufSize, pBuffer );
+ VS_FIXEDFILEINFO* pFixedFileInfo = NULL;
+ UINT nFixedFileSize = 0;
+ if( bRC )
+ VerQueryValueW( pBuffer, const_cast<LPWSTR>(L"\\"), (void**)&pFixedFileInfo, &nFixedFileSize );
+ if( pFixedFileInfo && pFixedFileInfo->dwSignature == 0xFEEF04BD )
+ nUspVersion = HIWORD(pFixedFileInfo->dwProductVersionMS) * 10000
+ + LOWORD(pFixedFileInfo->dwProductVersionMS);
+ }
+
+ // #i77976# USP>=1.0600 changed the need to manually align glyphs in their cells
+ if( nUspVersion >= 10600 )
+#endif
+ {
+ bManualCellAlign = false;
+ }
+
+ bUspInited = true;
+}
+
+UniscribeLayout::UniscribeLayout(HDC hDC, const ImplWinFontData& rWinFontData,
+ ImplWinFontEntry& rWinFontEntry, bool bUseOpenGL)
+: WinLayout(hDC, rWinFontData, rWinFontEntry, bUseOpenGL),
+ mpScriptItems( NULL ),
+ mpVisualItems( NULL ),
+ mnItemCount( 0 ),
+ mnCharCapacity( 0 ),
+ mpLogClusters( NULL ),
+ mpCharWidths( NULL ),
+ mnSubStringMin( 0 ),
+ mnGlyphCount( 0 ),
+ mnGlyphCapacity( 0 ),
+ mpGlyphAdvances( NULL ),
+ mpJustifications( NULL ),
+ mpOutGlyphs( NULL ),
+ mpGlyphOffsets( NULL ),
+ mpVisualAttrs( NULL ),
+ mpGlyphs2Chars( NULL ),
+ mnMinKashidaWidth( 0 ),
+ mnMinKashidaGlyph( 0 ),
+ mbDisableGlyphInjection( false ),
+ mbUseGLyphy( false )
+{
+ static bool bUseGLyphy = std::getenv("SAL_USE_GLYPHY") != NULL;
+ mbUseGLyphy = bUseGLyphy;
+}
+
+UniscribeLayout::~UniscribeLayout()
+{
+ delete[] mpScriptItems;
+ delete[] mpVisualItems;
+ delete[] mpLogClusters;
+ delete[] mpCharWidths;
+ delete[] mpOutGlyphs;
+ delete[] mpGlyphAdvances;
+ delete[] mpJustifications;
+ delete[] mpGlyphOffsets;
+ delete[] mpVisualAttrs;
+ delete[] mpGlyphs2Chars;
+}
+
+#if 0 // Don't remove -- useful for temporary SAL_ DEBUG when hacking on this
+
+namespace {
+
+template<typename IntegerType>
+OUString IntegerArrayToString(IntegerType *pWords, int n)
+{
+ OUString result = "{";
+ for (int i = 0; i < n; ++i)
+ {
+ if (i > 0)
+ result += ",";
+ if (i > 0 && i % 10 == 0)
+ result += OUString::number(i) + ":";
+ result += OUString::number(pWords[i]);
+ }
+ result += "}";
+
+ return result;
+}
+
+OUString GoffsetArrayToString(GOFFSET *pGoffsets, int n)
+{
+ OUString result = "{";
+ for (int i = 0; i < n; ++i)
+ {
+ if (i > 0)
+ result += ",";
+ if (i > 0 && i % 10 == 0)
+ result += OUString::number(i) + ":";
+ result += "(" + OUString::number(pGoffsets[i].du) + "," + OUString::number(pGoffsets[i].dv) + ")";
+ }
+ result += "}";
+
+ return result;
+}
+
+OUString VisAttrArrayToString(SCRIPT_VISATTR *pVisAttrs, int n)
+{
+ static const OUString JUSTIFICATION_NAME[] = {
+ "NONE",
+ "ARABIC_BLANK",
+ "CHARACTER",
+ "RESERVED1",
+ "BLANK",
+ "RESERVED2",
+ "RESERVED3",
+ "ARABIC_NORMAL",
+ "ARABIC_KASHIDA",
+ "ARABIC_ALEF",
+ "ARABIC_HA",
+ "ARABIC_RA",
+ "ARABIC_BA",
+ "ARABIC_BARA",
+ "ARABIC_SEEN",
+ "ARABIC_SEEN_M"
+ };
+
+ OUString result = "{";
+ for (int i = 0; i < n; ++i)
+ {
+ if (i > 0)
+ result += ",";
+ if (i > 0 && i % 10 == 0)
+ result += OUString::number(i) + ":";
+ result += OUString("{") + JUSTIFICATION_NAME[pVisAttrs[i].uJustification] + (pVisAttrs[i].fClusterStart ? OUString(",ClusterStart") : OUString()) + (pVisAttrs[i].fDiacritic ? OUString(",Diacritic") : OUString()) + OUString(pVisAttrs[i].fZeroWidth ? OUString(",ZeroWidth") : OUString()) + OUString("}");
+ }
+ result += "}";
+
+ return result;
+}
+
+} // anonymous namespace
+
+#endif // 0
+
+bool UniscribeLayout::LayoutText( ImplLayoutArgs& rArgs )
+{
+ msTheString = rArgs.mrStr;
+
+ // for a base layout only the context glyphs have to be dropped
+ // => when the whole string is involved there is no extra context
+ typedef std::vector<int> TIntVector;
+ TIntVector aDropChars;
+ if( rArgs.mnFlags & SalLayoutFlags::ForFallback )
+ {
+ // calculate superfluous context char positions
+ aDropChars.push_back(0);
+ aDropChars.push_back(rArgs.mrStr.getLength());
+ int nMin, nEnd;
+ bool bRTL;
+ for( rArgs.ResetPos(); rArgs.GetNextRun( &nMin, &nEnd, &bRTL ); )
+ {
+ aDropChars.push_back( nMin );
+ aDropChars.push_back( nEnd );
+ }
+ // prepare aDropChars for binary search which will allow to
+ // not bother with visual items that will be dropped anyway
+ std::sort( aDropChars.begin(), aDropChars.end() );
+ }
+
+ // prepare layout
+ // TODO: fix case when recyclying old UniscribeLayout object
+ mnMinCharPos = rArgs.mnMinCharPos;
+ mnEndCharPos = rArgs.mnEndCharPos;
+
+ // determine script items from string
+
+ // prepare itemization
+ // TODO: try to avoid itemization since it costs a lot of performance
+ SCRIPT_STATE aScriptState = {0,false,false,false,false,false,false,false,false,0,0};
+ aScriptState.uBidiLevel = bool(rArgs.mnFlags & SalLayoutFlags::BiDiRtl);
+ aScriptState.fOverrideDirection = bool(rArgs.mnFlags & SalLayoutFlags::BiDiStrong);
+ aScriptState.fDigitSubstitute = bool(rArgs.mnFlags & SalLayoutFlags::SubstituteDigits);
+ aScriptState.fArabicNumContext = aScriptState.fDigitSubstitute & aScriptState.uBidiLevel;
+ DWORD nLangId = 0; // TODO: get language from font
+ SCRIPT_CONTROL aScriptControl = {nLangId,false,false,false,false,false,false,false,false,0};
+ aScriptControl.fNeutralOverride = aScriptState.fOverrideDirection;
+ aScriptControl.fContextDigits = bool(rArgs.mnFlags & SalLayoutFlags::SubstituteDigits);
+ aScriptControl.fMergeNeutralItems = true;
+
+ // determine relevant substring and work only on it
+ // when Bidi status is unknown we need to look at the whole string though
+ mnSubStringMin = 0;
+ const int nLength = rArgs.mrStr.getLength();
+ const sal_Unicode *pStr = rArgs.mrStr.getStr();
+ int nSubStringEnd = nLength;
+ if( aScriptState.fOverrideDirection )
+ {
+ // TODO: limit substring to portion limits
+ mnSubStringMin = rArgs.mnMinCharPos - 8;
+ if( mnSubStringMin < 0 )
+ mnSubStringMin = 0;
+ nSubStringEnd = rArgs.mnEndCharPos + 8;
+ if( nSubStringEnd > nLength )
+ nSubStringEnd = nLength;
+
+ }
+ // now itemize the substring with its context
+ for( int nItemCapacity = 16;; nItemCapacity *= 8 )
+ {
+ mpScriptItems = new SCRIPT_ITEM[ nItemCapacity ];
+ HRESULT nRC = ScriptItemize(
+ reinterpret_cast<LPCWSTR>(pStr + mnSubStringMin), nSubStringEnd - mnSubStringMin,
+ nItemCapacity - 1, &aScriptControl, &aScriptState,
+ mpScriptItems, &mnItemCount );
+ if( !nRC ) // break loop when everything is correctly itemized
+ break;
+
+ // prepare bigger buffers for another itemization round
+ delete[] mpScriptItems;
+ mpScriptItems = NULL;
+ if( nRC != E_OUTOFMEMORY )
+ return false;
+ if( nItemCapacity > (nSubStringEnd - mnSubStringMin) + 16 )
+ return false;
+ }
+
+ // calculate the order of visual items
+ int nItem, i;
+
+ // adjust char positions by substring offset
+ for( nItem = 0; nItem <= mnItemCount; ++nItem )
+ mpScriptItems[ nItem ].iCharPos += mnSubStringMin;
+ // default visual item ordering
+ mpVisualItems = new VisualItem[ mnItemCount ];
+ for( nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ // initialize char specific item info
+ VisualItem& rVisualItem = mpVisualItems[ nItem ];
+ SCRIPT_ITEM* pScriptItem = &mpScriptItems[ nItem ];
+ rVisualItem.mpScriptItem = pScriptItem;
+ rVisualItem.mnMinCharPos = pScriptItem[0].iCharPos;
+ rVisualItem.mnEndCharPos = pScriptItem[1].iCharPos;
+ }
+
+ // reorder visual item order if needed
+ if( rArgs.mnFlags & SalLayoutFlags::BiDiStrong )
+ {
+ // force RTL item ordering if requested
+ if( rArgs.mnFlags & SalLayoutFlags::BiDiRtl )
+ {
+ VisualItem* pVI0 = &mpVisualItems[ 0 ];
+ VisualItem* pVI1 = &mpVisualItems[ mnItemCount ];
+ while( pVI0 < --pVI1 )
+ {
+ VisualItem aVtmp = *pVI0;
+ *(pVI0++) = *pVI1;
+ *pVI1 = aVtmp;
+ }
+ }
+ }
+ else if( mnItemCount > 1 )
+ {
+ // apply bidi algorithm's rule L2 on item level
+ // TODO: use faster L2 algorithm
+ int nMaxBidiLevel = 0;
+ VisualItem* pVI = &mpVisualItems[0];
+ VisualItem* const pVIend = pVI + mnItemCount;
+ for(; pVI < pVIend; ++pVI )
+ if( nMaxBidiLevel < pVI->mpScriptItem->a.s.uBidiLevel )
+ nMaxBidiLevel = pVI->mpScriptItem->a.s.uBidiLevel;
+
+ while( --nMaxBidiLevel >= 0 )
+ {
+ for( pVI = &mpVisualItems[0]; pVI < pVIend; )
+ {
+ // find item range that needs reordering
+ for(; pVI < pVIend; ++pVI )
+ if( nMaxBidiLevel < pVI->mpScriptItem->a.s.uBidiLevel )
+ break;
+ VisualItem* pVImin = pVI++;
+ for(; pVI < pVIend; ++pVI )
+ if( nMaxBidiLevel >= pVI->mpScriptItem->a.s.uBidiLevel )
+ break;
+ VisualItem* pVImax = pVI++;
+
+ // reverse order of items in this range
+ while( pVImin < --pVImax )
+ {
+ VisualItem aVtmp = *pVImin;
+ *(pVImin++) = *pVImax;
+ *pVImax = aVtmp;
+ }
+ }
+ }
+ }
+
+ // allocate arrays
+ // TODO: when reusing object reuse old allocations or delete them
+ // TODO: use only [nSubStringMin..nSubStringEnd) instead of [0..nSubStringEnd)
+ mnCharCapacity = nSubStringEnd;
+ mpLogClusters = new WORD[ mnCharCapacity ];
+ mpCharWidths = new int[ mnCharCapacity ];
+
+ mnGlyphCount = 0;
+ mnGlyphCapacity = 16 + 4 * (nSubStringEnd - mnSubStringMin); // worst case assumption
+ mpGlyphAdvances = new int[ mnGlyphCapacity ];
+ mpOutGlyphs = new WORD[ mnGlyphCapacity ];
+ mpGlyphOffsets = new GOFFSET[ mnGlyphCapacity ];
+ mpVisualAttrs = new SCRIPT_VISATTR[ mnGlyphCapacity ];
+
+ long nXOffset = 0;
+ for( int j = mnSubStringMin; j < nSubStringEnd; ++j )
+ mpCharWidths[j] = 0;
+
+ // layout script items
+ SCRIPT_CACHE& rScriptCache = GetScriptCache();
+ for( nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ VisualItem& rVisualItem = mpVisualItems[ nItem ];
+
+ // initialize glyph specific item info
+ rVisualItem.mnMinGlyphPos = mnGlyphCount;
+ rVisualItem.mnEndGlyphPos = 0;
+ rVisualItem.mnXOffset = nXOffset;
+
+ // shortcut ignorable items
+ if( (rArgs.mnEndCharPos <= rVisualItem.mnMinCharPos)
+ || (rArgs.mnMinCharPos >= rVisualItem.mnEndCharPos) )
+ {
+ for( int j = rVisualItem.mnMinCharPos; j < rVisualItem.mnEndCharPos; ++j )
+ mpLogClusters[j] = sal::static_int_cast<WORD>(~0U);
+ if (rArgs.mnMinCharPos >= rVisualItem.mnEndCharPos)
+ { // fdo#47553 adjust "guessed" min (maybe up to -8 off) to
+ // actual min so it can be used properly in GetNextGlyphs
+ if (mnSubStringMin < rVisualItem.mnEndCharPos)
+ mnSubStringMin = rVisualItem.mnEndCharPos;
+ }
+ continue;
+ }
+
+ // override bidi analysis if requested
+ if( rArgs.mnFlags & SalLayoutFlags::BiDiStrong )
+ {
+ // FIXME: is this intended ?
+ rVisualItem.mpScriptItem->a.fRTL = (aScriptState.uBidiLevel & 1);
+ rVisualItem.mpScriptItem->a.s.uBidiLevel = aScriptState.uBidiLevel;
+ rVisualItem.mpScriptItem->a.s.fOverrideDirection = aScriptState.fOverrideDirection;
+ }
+
+ // convert the unicodes to glyphs
+ int nGlyphCount = 0;
+ int nCharCount = rVisualItem.mnEndCharPos - rVisualItem.mnMinCharPos;
+ HRESULT nRC = ScriptShape( mhDC, &rScriptCache,
+ reinterpret_cast<LPCWSTR>(pStr + rVisualItem.mnMinCharPos),
+ nCharCount,
+ mnGlyphCapacity - rVisualItem.mnMinGlyphPos, // problem when >0xFFFF
+ &rVisualItem.mpScriptItem->a,
+ mpOutGlyphs + rVisualItem.mnMinGlyphPos,
+ mpLogClusters + rVisualItem.mnMinCharPos,
+ mpVisualAttrs + rVisualItem.mnMinGlyphPos,
+ &nGlyphCount );
+
+ // find and handle problems in the unicode to glyph conversion
+ if( nRC == USP_E_SCRIPT_NOT_IN_FONT )
+ {
+ // the whole visual item needs a fallback, but make sure that the next
+ // fallback request is limited to the characters in the original request
+ // => this is handled in ImplLayoutArgs::PrepareFallback()
+ rArgs.NeedFallback( rVisualItem.mnMinCharPos, rVisualItem.mnEndCharPos,
+ rVisualItem.IsRTL() );
+
+ // don't bother to do a default layout in a fallback level
+ if( rArgs.mnFlags & SalLayoutFlags::ForFallback )
+ continue;
+
+ // the primitive layout engine is good enough for the default layout
+ rVisualItem.mpScriptItem->a.eScript = SCRIPT_UNDEFINED;
+ nRC = ScriptShape( mhDC, &rScriptCache,
+ reinterpret_cast<LPCWSTR>(pStr + rVisualItem.mnMinCharPos),
+ nCharCount,
+ mnGlyphCapacity - rVisualItem.mnMinGlyphPos,
+ &rVisualItem.mpScriptItem->a,
+ mpOutGlyphs + rVisualItem.mnMinGlyphPos,
+ mpLogClusters + rVisualItem.mnMinCharPos,
+ mpVisualAttrs + rVisualItem.mnMinGlyphPos,
+ &nGlyphCount );
+
+ if( nRC != 0 )
+ continue;
+
+ }
+ else if( nRC != 0 )
+ // something undefined happened => give up for this visual item
+ continue;
+ else // if( nRC == 0 )
+ {
+ // check if there are any NotDef glyphs
+ for( i = 0; i < nGlyphCount; ++i )
+ if( 0 == mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] )
+ break;
+ if( i < nGlyphCount )
+ {
+ // clip charpos limits to the layout string without context
+ int nMinCharPos = rVisualItem.mnMinCharPos;
+ if( nMinCharPos < rArgs.mnMinCharPos )
+ nMinCharPos = rArgs.mnMinCharPos;
+ int nEndCharPos = rVisualItem.mnEndCharPos;
+ if( nEndCharPos > rArgs.mnEndCharPos )
+ nEndCharPos = rArgs.mnEndCharPos;
+ // request fallback for individual NotDef glyphs
+ do
+ {
+ // ignore non-NotDef glyphs
+ if( 0 != mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] )
+ continue;
+ mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = DROPPED_OUTGLYPH;
+ // request fallback for the whole cell that resulted in a NotDef glyph
+ // TODO: optimize algorithm
+ const bool bRTL = rVisualItem.IsRTL();
+ if( !bRTL )
+ {
+ // request fallback for the left-to-right cell
+ for( int c = nMinCharPos; c < nEndCharPos; ++c )
+ {
+ if( mpLogClusters[ c ] == i )
+ {
+ // #i55716# skip WORDJOINER
+ if( pStr[ c ] == 0x2060 )
+ mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = 1;
+ else
+ rArgs.NeedFallback( c, false );
+ }
+ }
+ }
+ else
+ {
+ // request fallback for the right to left cell
+ for( int c = nEndCharPos; --c >= nMinCharPos; )
+ {
+ if( mpLogClusters[ c ] == i )
+ {
+ // #i55716# skip WORDJOINER
+ if( pStr[ c ] == 0x2060 )
+ mpOutGlyphs[ i + rVisualItem.mnMinGlyphPos ] = 1;
+ else
+ rArgs.NeedFallback( c, true );
+ }
+ }
+ }
+ } while( ++i < nGlyphCount );
+ }
+ }
+
+ // now place the glyphs
+ nRC = ScriptPlace( mhDC, &rScriptCache,
+ mpOutGlyphs + rVisualItem.mnMinGlyphPos,
+ nGlyphCount,
+ mpVisualAttrs + rVisualItem.mnMinGlyphPos,
+ &rVisualItem.mpScriptItem->a,
+ mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
+ mpGlyphOffsets + rVisualItem.mnMinGlyphPos,
+ &rVisualItem.maABCWidths );
+
+ if( nRC != 0 )
+ continue;
+
+ // calculate the logical char widths from the glyph layout
+ nRC = ScriptGetLogicalWidths(
+ &rVisualItem.mpScriptItem->a,
+ nCharCount, nGlyphCount,
+ mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
+ mpLogClusters + rVisualItem.mnMinCharPos,
+ mpVisualAttrs + rVisualItem.mnMinGlyphPos,
+ mpCharWidths + rVisualItem.mnMinCharPos );
+
+ // update the glyph counters
+ mnGlyphCount += nGlyphCount;
+ rVisualItem.mnEndGlyphPos = mnGlyphCount;
+
+ // update nXOffset
+ int nEndGlyphPos;
+ if( GetItemSubrange( rVisualItem, i, nEndGlyphPos ) )
+ for(; i < nEndGlyphPos; ++i )
+ nXOffset += mpGlyphAdvances[ i ];
+
+ // TODO: shrink glyphpos limits to match charpos/fallback limits
+ //pVI->mnMinGlyphPos = nMinGlyphPos;
+ //pVI->mnEndGlyphPos = nEndGlyphPos;
+
+ // drop the superfluous context glyphs
+ TIntVector::const_iterator it = aDropChars.begin();
+ while( it != aDropChars.end() )
+ {
+ // find matching "drop range"
+ int nMinDropPos = *(it++); // begin of drop range
+ if( nMinDropPos >= rVisualItem.mnEndCharPos )
+ break;
+ int nEndDropPos = *(it++); // end of drop range
+ if( nEndDropPos <= rVisualItem.mnMinCharPos )
+ continue;
+ // clip "drop range" to visual item's char range
+ if( nMinDropPos <= rVisualItem.mnMinCharPos )
+ {
+ nMinDropPos = rVisualItem.mnMinCharPos;
+ // drop the whole visual item if possible
+ if( nEndDropPos >= rVisualItem.mnEndCharPos )
+ {
+ rVisualItem.mnEndGlyphPos = 0;
+ break;
+ }
+ }
+ if( nEndDropPos > rVisualItem.mnEndCharPos )
+ nEndDropPos = rVisualItem.mnEndCharPos;
+
+ // drop the glyphs which correspond to the charpos range
+ // drop the corresponding glyphs in the cluster
+ for( int c = nMinDropPos; c < nEndDropPos; ++c )
+ {
+ int nGlyphPos = mpLogClusters[c] + rVisualItem.mnMinGlyphPos;
+ // no need to bother when the cluster was already dropped
+ if( mpOutGlyphs[ nGlyphPos ] != DROPPED_OUTGLYPH )
+ {
+ for(;;)
+ {
+ mpOutGlyphs[ nGlyphPos ] = DROPPED_OUTGLYPH;
+ // until the end of visual item
+ if( ++nGlyphPos >= rVisualItem.mnEndGlyphPos )
+ break;
+ // until the next cluster start
+ if( mpVisualAttrs[ nGlyphPos ].fClusterStart )
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // scale layout metrics if needed
+ // TODO: does it make the code more simple if the metric scaling
+ // is moved to the methods that need metric scaling (e.g. FillDXArray())?
+ if( mfFontScale != 1.0 )
+ {
+ mnBaseAdv = (int)((double)mnBaseAdv*mfFontScale);
+
+ for( i = 0; i < mnItemCount; ++i )
+ mpVisualItems[i].mnXOffset = (int)((double)mpVisualItems[i].mnXOffset*mfFontScale);
+
+ mnBaseAdv = (int)((double)mnBaseAdv*mfFontScale);
+ for( i = 0; i < mnGlyphCount; ++i )
+ {
+ mpGlyphAdvances[i] = (int)(mpGlyphAdvances[i] * mfFontScale);
+ mpGlyphOffsets[i].du = (LONG)(mpGlyphOffsets[i].du * mfFontScale);
+ mpGlyphOffsets[i].dv = (LONG)(mpGlyphOffsets[i].dv * mfFontScale);
+ // mpJustifications are still NULL
+ }
+
+ for( i = mnSubStringMin; i < nSubStringEnd; ++i )
+ mpCharWidths[i] = (int)(mpCharWidths[i] * mfFontScale);
+ }
+
+ return true;
+}
+
+// calculate the range of relevant glyphs for this visual item
+bool UniscribeLayout::GetItemSubrange( const VisualItem& rVisualItem,
+ int& rMinGlyphPos, int& rEndGlyphPos ) const
+{
+ // return early when nothing of interest in this item
+ if( rVisualItem.IsEmpty()
+ || (rVisualItem.mnEndCharPos <= mnMinCharPos)
+ || (mnEndCharPos <= rVisualItem.mnMinCharPos) )
+ return false;
+
+ // default: subrange is complete range
+ rMinGlyphPos = rVisualItem.mnMinGlyphPos;
+ rEndGlyphPos = rVisualItem.mnEndGlyphPos;
+
+ // return early when the whole item is of interest
+ if( (mnMinCharPos <= rVisualItem.mnMinCharPos)
+ && (rVisualItem.mnEndCharPos <= mnEndCharPos ) )
+ return true;
+
+ // get glyph range from char range by looking at cluster boundries
+ // TODO: optimize for case that LTR/RTL correspond to monotonous glyph indexes
+ rMinGlyphPos = rVisualItem.mnEndGlyphPos;
+ int nMaxGlyphPos = 0;
+
+ int i = mnMinCharPos;
+ if( i < rVisualItem.mnMinCharPos )
+ i = rVisualItem.mnMinCharPos;
+ int nCharPosLimit = rVisualItem.mnEndCharPos;
+ if( nCharPosLimit > mnEndCharPos )
+ nCharPosLimit = mnEndCharPos;
+ for(; i < nCharPosLimit; ++i )
+ {
+ int n = mpLogClusters[ i ] + rVisualItem.mnMinGlyphPos;
+ if( rMinGlyphPos > n )
+ rMinGlyphPos = n;
+ if( nMaxGlyphPos < n )
+ nMaxGlyphPos = n;
+ }
+ if (nMaxGlyphPos > rVisualItem.mnEndGlyphPos)
+ nMaxGlyphPos = rVisualItem.mnEndGlyphPos - 1;
+
+ // extend the glyph range to account for all glyphs in referenced clusters
+ if( !rVisualItem.IsRTL() ) // LTR-item
+ {
+ // extend to rightmost glyph of rightmost referenced cluster
+ for( i = nMaxGlyphPos; ++i < rVisualItem.mnEndGlyphPos; nMaxGlyphPos = i )
+ if( mpVisualAttrs[i].fClusterStart )
+ break;
+ }
+ else // RTL-item
+ {
+ // extend to leftmost glyph of leftmost referenced cluster
+ for( i = rMinGlyphPos; --i >= rVisualItem.mnMinGlyphPos; rMinGlyphPos = i )
+ if( mpVisualAttrs[i].fClusterStart )
+ break;
+ }
+ rEndGlyphPos = nMaxGlyphPos + 1;
+
+ return true;
+}
+
+int UniscribeLayout::GetNextGlyphs( int nLen, sal_GlyphId* pGlyphs, Point& rPos,
+ int& nStartx8, DeviceCoordinate* pGlyphAdvances, int* pCharPosAry,
+ const PhysicalFontFace** /*pFallbackFonts*/ ) const
+{
+ // HACK to allow fake-glyph insertion (e.g. for kashidas)
+ // TODO: use iterator idiom instead of GetNextGlyphs(...)
+ // TODO: else make sure that the limit for glyph injection is sufficient (currently 256)
+ int nSubIter = nStartx8 & 0xff;
+ int nStart = nStartx8 >> 8;
+
+ // check the glyph iterator
+ if( nStart > mnGlyphCount ) // nStart>MAX means no more glyphs
+ return 0;
+
+ // find the visual item for the nStart glyph position
+ int nItem = 0;
+ const VisualItem* pVI = mpVisualItems;
+ if( nStart <= 0 ) // nStart<=0 requests the first visible glyph
+ {
+ // find first visible item
+ for(; nItem < mnItemCount; ++nItem, ++pVI )
+ if( !pVI->IsEmpty() )
+ break;
+ // it is possible that there are glyphs but no valid visual item
+ // TODO: get rid of these visual items more early
+ if( nItem < mnItemCount )
+ nStart = pVI->mnMinGlyphPos;
+ }
+ else //if( nStart > 0 ) // nStart>0 means absolute glyph pos +1
+ {
+ --nStart;
+
+ // find matching item
+ for(; nItem < mnItemCount; ++nItem, ++pVI )
+ if( (nStart >= pVI->mnMinGlyphPos)
+ && (nStart < pVI->mnEndGlyphPos) )
+ break;
+ }
+
+ // after the last visual item there are no more glyphs
+ if( (nItem >= mnItemCount) || (nStart < 0) )
+ {
+ nStartx8 = (mnGlyphCount + 1) << 8;
+ return 0;
+ }
+
+ // calculate the first glyph in the next visual item
+ int nNextItemStart = mnGlyphCount;
+ while( ++nItem < mnItemCount )
+ {
+ if( mpVisualItems[nItem].IsEmpty() )
+ continue;
+ nNextItemStart = mpVisualItems[nItem].mnMinGlyphPos;
+ break;
+ }
+
+ // get the range of relevant glyphs in this visual item
+ int nMinGlyphPos, nEndGlyphPos;
+ bool bRC = GetItemSubrange( *pVI, nMinGlyphPos, nEndGlyphPos );
+ DBG_ASSERT( bRC, "USPLayout::GNG GISR() returned false" );
+ if( !bRC )
+ {
+ nStartx8 = (mnGlyphCount + 1) << 8;
+ return 0;
+ }
+
+ // make sure nStart is inside the range of relevant glyphs
+ if( nStart < nMinGlyphPos )
+ nStart = nMinGlyphPos;
+
+ // calculate the start glyph xoffset relative to layout's base position,
+ // advance to next visual glyph position by using adjusted glyph widths
+ // TODO: speed up the calculation for nStart!=0 case by using rPos as a cache
+ long nXOffset = pVI->mnXOffset;
+ const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
+ for( int i = nMinGlyphPos; i < nStart; ++i )
+ nXOffset += pGlyphWidths[ i ];
+
+ // adjust the nXOffset relative to glyph cluster start
+ int c = mnMinCharPos;
+ if( !pVI->IsRTL() ) // LTR-case
+ {
+ // LTR case: subtract the remainder of the cell from xoffset
+ int nTmpIndex = mpLogClusters[c];
+ while( (--c >= pVI->mnMinCharPos)
+ && (nTmpIndex == mpLogClusters[c]) )
+ nXOffset -= mpCharWidths[c];
+ }
+ else // RTL-case
+ {
+ // RTL case: add the remainder of the cell from xoffset
+ int nTmpIndex = mpLogClusters[ pVI->mnEndCharPos - 1 ];
+ while( (--c >= pVI->mnMinCharPos)
+ && (nTmpIndex == mpLogClusters[c]) )
+ nXOffset += mpCharWidths[c];
+
+ // adjust the xoffset if justified glyphs are not positioned at their justified positions yet
+ if( mpJustifications && !bManualCellAlign )
+ nXOffset += mpJustifications[ nStart ] - mpGlyphAdvances[ nStart ];
+ }
+
+ // create mpGlyphs2Chars[] if it is needed later
+ if( pCharPosAry && !mpGlyphs2Chars )
+ {
+ // create and reset the new array
+ mpGlyphs2Chars = new int[ mnGlyphCapacity ];
+ static const int CHARPOS_NONE = -1;
+ for( int i = 0; i < mnGlyphCount; ++i )
+ mpGlyphs2Chars[i] = CHARPOS_NONE;
+ // calculate the char->glyph mapping
+ for( nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ // ignore invisible visual items
+ const VisualItem& rVI = mpVisualItems[ nItem ];
+ if( rVI.IsEmpty() )
+ continue;
+
+ //Resolves: fdo#33090 Ensure that all glyph slots, even if 0-width
+ //or empty due to combining chars etc, map back to a character
+ //position so that iterating over glyph slots one at a time for
+ //glyph fallback can keep context as to what characters are the
+ //inputs that caused a missing glyph in a given font.
+
+ //See: fdo#46923/fdo#46896/fdo#46750 for extra complexities
+ {
+ int dir = 1;
+ int out = rVI.mnMinCharPos;
+ if (rVI.IsRTL())
+ {
+ dir = -1;
+ out = rVI.mnEndCharPos-1;
+ }
+ for(c = rVI.mnMinCharPos; c < rVI.mnEndCharPos; ++c)
+ {
+ int i = out - mnSubStringMin;
+ mpGlyphs2Chars[i] = c;
+ out += dir;
+ }
+ }
+
+ // calculate the mapping by using mpLogClusters[]
+ // mpGlyphs2Chars[] should obey the logical order
+ // => reversing the loop does this by overwriting higher logicals
+ for( c = rVI.mnEndCharPos; --c >= rVI.mnMinCharPos; )
+ {
+ int i = mpLogClusters[c] + rVI.mnMinGlyphPos;
+ mpGlyphs2Chars[i] = c;
+ }
+ // use a heuristic to fill the gaps in the glyphs2chars array
+ c = !rVI.IsRTL() ? rVI.mnMinCharPos : rVI.mnEndCharPos - 1;
+ for( int i = rVI.mnMinGlyphPos; i < rVI.mnEndGlyphPos; ++i ) {
+ if( mpGlyphs2Chars[i] == CHARPOS_NONE )
+ mpGlyphs2Chars[i] = c;
+ else
+ c = mpGlyphs2Chars[i];
+ }
+ }
+ }
+
+ // calculate the absolute position of the first result glyph in pixel units
+ const GOFFSET aGOffset = mpGlyphOffsets[ nStart ];
+ Point aRelativePos( nXOffset + aGOffset.du, -aGOffset.dv );
+ rPos = GetDrawPosition( aRelativePos );
+
+ // fill the result arrays
+ int nCount = 0;
+ while( nCount < nLen )
+ {
+ // prepare return values
+ sal_GlyphId aGlyphId = mpOutGlyphs[ nStart ];
+ int nGlyphWidth = pGlyphWidths[ nStart ];
+ int nCharPos = -1; // no need to determine charpos
+ if( mpGlyphs2Chars ) // unless explicitly requested+provided
+ {
+ nCharPos = mpGlyphs2Chars[ nStart ];
+ assert(-1 != nCharPos);
+ }
+
+ // inject kashida glyphs if needed
+ if( !mbDisableGlyphInjection
+ && mpJustifications
+ && mnMinKashidaWidth
+ && mpVisualAttrs[nStart].uJustification >= SCRIPT_JUSTIFY_ARABIC_NORMAL )
+ {
+ // prepare draw position adjustment
+ int nExtraOfs = (nSubIter++) * mnMinKashidaWidth;
+ // calculate space available for the injected glyphs
+ nGlyphWidth = mpGlyphAdvances[ nStart ];
+ const int nExtraWidth = mpJustifications[ nStart ] - nGlyphWidth;
+ const int nToFillWidth = nExtraWidth - nExtraOfs;
+ if( (4*nToFillWidth >= mnMinKashidaWidth) // prevent glyph-injection if there is no room
+ || ((nSubIter > 1) && (nToFillWidth > 0)) ) // unless they can overlap with others
+ {
+ // handle if there is not sufficient room for a full glyph
+ if( nToFillWidth < mnMinKashidaWidth )
+ {
+ // overlap it with the previously injected glyph if possible
+ int nOverlap = mnMinKashidaWidth - nToFillWidth;
+ // else overlap it with both neighboring glyphs
+ if( nSubIter <= 1 )
+ nOverlap /= 2;
+ nExtraOfs -= nOverlap;
+ }
+ nGlyphWidth = mnMinKashidaWidth;
+ aGlyphId = mnMinKashidaGlyph;
+ nCharPos = -1;
+ }
+ else
+ {
+ nExtraOfs += nToFillWidth; // at right of cell
+ nSubIter = 0; // done with glyph injection
+ }
+ if( !bManualCellAlign )
+ nExtraOfs -= nExtraWidth; // adjust for right-aligned cells
+
+ // adjust the draw position for the injected-glyphs case
+ if( nExtraOfs )
+ {
+ aRelativePos.X() += nExtraOfs;
+ rPos = GetDrawPosition( aRelativePos );
+ }
+ }
+
+ // update return values
+ if( (mnLayoutFlags & SalLayoutFlags::Vertical) &&
+ nCharPos != -1 )
+ aGlyphId |= GetVerticalFlags( msTheString[nCharPos] );
+ *(pGlyphs++) = aGlyphId;
+ if( pGlyphAdvances )
+ *(pGlyphAdvances++) = nGlyphWidth;
+ if( pCharPosAry )
+ *(pCharPosAry++) = nCharPos;
+
+ // increment counter of returned glyphs
+ ++nCount;
+
+ // reduce code complexity by returning early in glyph-injection case
+ if( nSubIter != 0 )
+ break;
+
+ // stop after the last visible glyph in this visual item
+ if( ++nStart >= nEndGlyphPos )
+ {
+ nStart = nNextItemStart;
+ break;
+ }
+
+ // RTL-justified glyph positioning is not easy
+ // simplify the code by just returning only one glyph at a time
+ if( mpJustifications && pVI->IsRTL() )
+ break;
+
+ // stop when the x-position of the next glyph is unexpected
+ if( !pGlyphAdvances )
+ if( (mpGlyphOffsets && (mpGlyphOffsets[nStart].du != aGOffset.du) )
+ || (mpJustifications && (mpJustifications[nStart] != mpGlyphAdvances[nStart]) ) )
+ break;
+
+ // stop when the y-position of the next glyph is unexpected
+ if( mpGlyphOffsets && (mpGlyphOffsets[nStart].dv != aGOffset.dv) )
+ break;
+ }
+
+ ++nStart;
+ nStartx8 = (nStart << 8) + nSubIter;
+ return nCount;
+}
+
+void UniscribeLayout::MoveGlyph( int nStartx8, long nNewXPos )
+{
+ DBG_ASSERT( !(nStartx8 & 0xff), "USP::MoveGlyph(): glyph injection not disabled!" );
+ int nStart = nStartx8 >> 8;
+ if( nStart > mnGlyphCount )
+ return;
+
+ VisualItem* pVI = mpVisualItems;
+ int nMinGlyphPos = 0, nEndGlyphPos;
+ if( nStart == 0 ) // nStart==0 for first visible glyph
+ {
+ for( int i = mnItemCount; --i >= 0; ++pVI )
+ if( GetItemSubrange( *pVI, nMinGlyphPos, nEndGlyphPos ) )
+ break;
+ nStart = nMinGlyphPos;
+ DBG_ASSERT( nStart <= mnGlyphCount, "USPLayout::MoveG overflow" );
+ }
+ else //if( nStart > 0 ) // nStart>0 means absolute_glyphpos+1
+ {
+ --nStart;
+ for( int i = mnItemCount; --i >= 0; ++pVI )
+ if( (nStart >= pVI->mnMinGlyphPos) && (nStart < pVI->mnEndGlyphPos) )
+ break;
+ bool bRC = GetItemSubrange( *pVI, nMinGlyphPos, nEndGlyphPos );
+ (void)bRC; // avoid var-not-used warning
+ DBG_ASSERT( bRC, "USPLayout::MoveG GISR() returned false" );
+ }
+
+ long nDelta = nNewXPos - pVI->mnXOffset;
+ if( nStart > nMinGlyphPos )
+ {
+ // move the glyph by expanding its left glyph but ignore dropped glyphs
+ int i, nLastUndropped = nMinGlyphPos - 1;
+ for( i = nMinGlyphPos; i < nStart; ++i )
+ {
+ if (mpOutGlyphs[i] != DROPPED_OUTGLYPH)
+ {
+ nDelta -= (mpJustifications)? mpJustifications[ i ] : mpGlyphAdvances[ i ];
+ nLastUndropped = i;
+ }
+ }
+ if (nLastUndropped >= nMinGlyphPos)
+ {
+ mpGlyphAdvances[ nLastUndropped ] += nDelta;
+ if (mpJustifications) mpJustifications[ nLastUndropped ] += nDelta;
+ }
+ else
+ {
+ pVI->mnXOffset += nDelta;
+ }
+ }
+ else
+ {
+ // move the visual item by having an offset
+ pVI->mnXOffset += nDelta;
+ }
+ // move subsequent items - this often isn't necessary because subsequent
+ // moves will correct subsequent items. However, if there is a contiguous
+ // range not involving fallback which spans items, this will be needed
+ while (++pVI - mpVisualItems < mnItemCount)
+ {
+ pVI->mnXOffset += nDelta;
+ }
+}
+
+void UniscribeLayout::DropGlyph( int nStartx8 )
+{
+ DBG_ASSERT( !(nStartx8 & 0xff), "USP::DropGlyph(): glyph injection not disabled!" );
+ int nStart = nStartx8 >> 8;
+ assert(nStart <= mnGlyphCount);
+
+ if( nStart > 0 ) // nStart>0 means absolute glyph pos + 1
+ --nStart;
+ else // nStart<=0 for first visible glyph
+ {
+ VisualItem* pVI = mpVisualItems;
+ for( int i = mnItemCount, nDummy; --i >= 0; ++pVI )
+ if( GetItemSubrange( *pVI, nStart, nDummy ) )
+ break;
+ assert(nStart <= mnGlyphCount);
+
+ int j = pVI->mnMinGlyphPos;
+ while (j < mnGlyphCount && mpOutGlyphs[j] == DROPPED_OUTGLYPH) j++;
+ if (j == nStart)
+ {
+ pVI->mnXOffset += ((mpJustifications)? mpJustifications[nStart] : mpGlyphAdvances[nStart]);
+ }
+ }
+
+ mpOutGlyphs[ nStart ] = DROPPED_OUTGLYPH;
+}
+
+void UniscribeLayout::Simplify( bool /*bIsBase*/ )
+{
+ static const WCHAR cDroppedGlyph = DROPPED_OUTGLYPH;
+ int i;
+ // if there are no dropped glyphs don't bother
+ for( i = 0; i < mnGlyphCount; ++i )
+ if( mpOutGlyphs[ i ] == cDroppedGlyph )
+ break;
+ if( i >= mnGlyphCount )
+ return;
+
+ // prepare for sparse layout
+ // => make sure mpGlyphs2Chars[] exists
+ if( !mpGlyphs2Chars )
+ {
+ mpGlyphs2Chars = new int[ mnGlyphCapacity ];
+ for( i = 0; i < mnGlyphCount; ++i )
+ mpGlyphs2Chars[ i ] = -1;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ // skip invisible items
+ VisualItem& rVI = mpVisualItems[ nItem ];
+ if( rVI.IsEmpty() )
+ continue;
+ for( i = rVI.mnEndCharPos; --i >= rVI.mnMinCharPos; )
+ {
+ int j = mpLogClusters[ i ] + rVI.mnMinGlyphPos;
+ mpGlyphs2Chars[ j ] = i;
+ }
+ }
+ }
+
+ // remove the dropped glyphs
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ VisualItem& rVI = mpVisualItems[ nItem ];
+ if( rVI.IsEmpty() )
+ continue;
+
+ // mark replaced character widths
+ for( i = rVI.mnMinCharPos; i < rVI.mnEndCharPos; ++i )
+ {
+ int j = mpLogClusters[ i ] + rVI.mnMinGlyphPos;
+ if( mpOutGlyphs[ j ] == cDroppedGlyph )
+ mpCharWidths[ i ] = 0;
+ }
+
+ // handle dropped glyphs at start of visual item
+ int nMinGlyphPos, nEndGlyphPos, nOrigMinGlyphPos = rVI.mnMinGlyphPos;
+ GetItemSubrange( rVI, nMinGlyphPos, nEndGlyphPos );
+ i = nMinGlyphPos;
+ while( (i < nEndGlyphPos) && (mpOutGlyphs[i] == cDroppedGlyph) )
+ {
+ rVI.mnMinGlyphPos = ++i;
+ }
+
+ // when all glyphs in item got dropped mark it as empty
+ if( i >= nEndGlyphPos )
+ {
+ rVI.mnEndGlyphPos = 0;
+ continue;
+ }
+ // If there are still glyphs in the cluster and mnMinGlyphPos
+ // has changed then we need to remove the dropped glyphs at start
+ // to correct logClusters, which is unsigned and relative to the
+ // item start.
+ if (rVI.mnMinGlyphPos != nOrigMinGlyphPos)
+ {
+ // drop any glyphs in the visual item outside the range
+ for (i = nOrigMinGlyphPos; i < nMinGlyphPos; i++)
+ mpOutGlyphs[ i ] = cDroppedGlyph;
+ rVI.mnMinGlyphPos = i = nOrigMinGlyphPos;
+ }
+
+ // handle dropped glyphs in the middle of visual item
+ for(; i < nEndGlyphPos; ++i )
+ if( mpOutGlyphs[ i ] == cDroppedGlyph )
+ break;
+ int j = i;
+ while( ++i < nEndGlyphPos )
+ {
+ if( mpOutGlyphs[ i ] == cDroppedGlyph )
+ continue;
+ mpOutGlyphs[ j ] = mpOutGlyphs[ i ];
+ mpGlyphOffsets[ j ] = mpGlyphOffsets[ i ];
+ mpVisualAttrs[ j ] = mpVisualAttrs[ i ];
+ mpGlyphAdvances[ j ] = mpGlyphAdvances[ i ];
+ if( mpJustifications )
+ mpJustifications[ j ] = mpJustifications[ i ];
+ const int k = mpGlyphs2Chars[ i ];
+ mpGlyphs2Chars[ j ] = k;
+ const int nRelGlyphPos = (j++) - rVI.mnMinGlyphPos;
+ if( k < 0) // extra glyphs are already mapped
+ continue;
+ mpLogClusters[ k ] = static_cast<WORD>(nRelGlyphPos);
+ }
+
+ rVI.mnEndGlyphPos = j;
+ }
+}
+
+void UniscribeLayout::DrawTextImpl(HDC hDC) const
+{
+ HFONT hOrigFont = DisableFontScaling();
+
+ int nBaseClusterOffset = 0;
+ int nBaseGlyphPos = -1;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ const VisualItem& rVisualItem = mpVisualItems[ nItem ];
+
+ // skip if there is nothing to display
+ int nMinGlyphPos, nEndGlyphPos;
+ if( !GetItemSubrange( rVisualItem, nMinGlyphPos, nEndGlyphPos ) )
+ continue;
+
+ if( nBaseGlyphPos < 0 )
+ {
+ // adjust draw position relative to cluster start
+ if( rVisualItem.IsRTL() )
+ nBaseGlyphPos = nEndGlyphPos - 1;
+ else
+ nBaseGlyphPos = nMinGlyphPos;
+
+ int i = mnMinCharPos;
+ while( (--i >= rVisualItem.mnMinCharPos)
+ && (nBaseGlyphPos == mpLogClusters[i]) )
+ nBaseClusterOffset += mpCharWidths[i];
+
+ if( !rVisualItem.IsRTL() )
+ nBaseClusterOffset = -nBaseClusterOffset;
+ }
+
+ // now draw the matching glyphs in this item
+ Point aRelPos( rVisualItem.mnXOffset + nBaseClusterOffset, 0 );
+ Point aPos = GetDrawPosition( aRelPos );
+ SCRIPT_CACHE& rScriptCache = GetScriptCache();
+ ScriptTextOut(hDC, &rScriptCache,
+ aPos.X(), aPos.Y(), 0, NULL,
+ &rVisualItem.mpScriptItem->a, NULL, 0,
+ mpOutGlyphs + nMinGlyphPos,
+ nEndGlyphPos - nMinGlyphPos,
+ mpGlyphAdvances + nMinGlyphPos,
+ mpJustifications ? mpJustifications + nMinGlyphPos : NULL,
+ mpGlyphOffsets + nMinGlyphPos);
+ }
+
+ if( hOrigFont )
+ DeleteFont(SelectFont(hDC, hOrigFont));
+}
+
+bool UniscribeLayout::CacheGlyphs(SalGraphics& rGraphics) const
+{
+ static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == NULL);
+
+ if (!bDoGlyphCaching)
+ return false;
+
+ if (mbUseGLyphy)
+ {
+ if (!mrWinFontEntry.maMetric.mbTrueTypeFont)
+ return false;
+
+ mrWinFontEntry.setupGLyphy(mhDC);
+
+ for (int i = 0; i < mnGlyphCount; i++)
+ {
+ if (mpOutGlyphs[i] == DROPPED_OUTGLYPH)
+ continue;
+
+ glyph_info_t aGI;
+ VCL_GL_INFO("Calling demo_font_lookup_glyph");
+ demo_font_lookup_glyph( mrWinFontEntry.mpGLyphyFont, mpOutGlyphs[i], &aGI );
+ }
+ }
+ else
+ {
+ for (int i = 0; i < mnGlyphCount; i++)
+ {
+ if (mrWinFontEntry.GlyphIsCached(mpOutGlyphs[i]))
+ continue;
+
+ if (!mrWinFontEntry.AddChunkOfGlyphs(mpOutGlyphs[i], *this, rGraphics))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool UniscribeLayout::DrawCachedGlyphsUsingGLyphy(SalGraphics& rGraphics) const
+{
+ WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics);
+
+ Rectangle aRect;
+ GetBoundRect(rGraphics, aRect);
+
+ WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(rWinGraphics.mpImpl.get());
+ if (!pImpl)
+ return false;
+
+ pImpl->PreDraw();
+
+ rGraphics.GetOpenGLContext()->UseNoProgram();
+ glUseProgram( mrWinFontEntry.mnGLyphyProgram );
+ CHECK_GL_ERROR();
+ demo_atlas_set_uniforms( mrWinFontEntry.mpGLyphyAtlas );
+
+ GLint nLoc;
+
+ nLoc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_debug" );
+ CHECK_GL_ERROR();
+ glUniform1f( nLoc, 0 ); // FIXME: Try to get the "debug" thing displayed first
+ CHECK_GL_ERROR();
+
+ nLoc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_contrast" );
+ CHECK_GL_ERROR();
+ glUniform1f( nLoc, 1 );
+ CHECK_GL_ERROR();
+
+ nLoc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_gamma_adjust" );
+ CHECK_GL_ERROR();
+ glUniform1f( nLoc, 1 );
+ CHECK_GL_ERROR();
+
+ nLoc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_outline" );
+ CHECK_GL_ERROR();
+ glUniform1f( nLoc, false );
+ CHECK_GL_ERROR();
+
+ nLoc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_outline_thickness" );
+ CHECK_GL_ERROR();
+ glUniform1f( nLoc, 1 );
+ CHECK_GL_ERROR();
+
+ nLoc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_boldness" );
+ CHECK_GL_ERROR();
+ glUniform1f( nLoc, 0 );
+ CHECK_GL_ERROR();
+
+ // FIXME: This code snippet is mostly copied from the one in
+ // UniscribeLayout::DrawTextImpl. Should be factored out.
+ int nBaseClusterOffset = 0;
+ int nBaseGlyphPos = -1;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ const VisualItem& rVisualItem = mpVisualItems[ nItem ];
+
+ // skip if there is nothing to display
+ int nMinGlyphPos, nEndGlyphPos;
+ if( !GetItemSubrange( rVisualItem, nMinGlyphPos, nEndGlyphPos ) )
+ continue;
+
+ if( nBaseGlyphPos < 0 )
+ {
+ // adjust draw position relative to cluster start
+ if( rVisualItem.IsRTL() )
+ nBaseGlyphPos = nEndGlyphPos - 1;
+ else
+ nBaseGlyphPos = nMinGlyphPos;
+
+ int i = mnMinCharPos;
+ while( (--i >= rVisualItem.mnMinCharPos)
+ && (nBaseGlyphPos == mpLogClusters[i]) )
+ nBaseClusterOffset += mpCharWidths[i];
+
+ if( !rVisualItem.IsRTL() )
+ nBaseClusterOffset = -nBaseClusterOffset;
+ }
+
+ // now draw the matching glyphs in this item
+ Point aRelPos( rVisualItem.mnXOffset + nBaseClusterOffset, 0 );
+ Point aPos = GetDrawPosition( aRelPos );
+
+ int nAdvance = 0;
+
+ // This has to be in sync with UniscribeLayout::FillDXArray(), so that
+ // actual and reported glyph positions (used for e.g. cursor caret
+ // positioning) match.
+ const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
+
+ double font_size = mrWinFontEntry.maFontSelData.mfExactHeight; // ???
+
+ // font_size = 1; // ???
+
+ std::vector<glyph_vertex_t> vertices;
+ for (int i = nMinGlyphPos; i < nEndGlyphPos; i++)
+ {
+ // Ignore dropped glyphs.
+ if (mpOutGlyphs[i] == DROPPED_OUTGLYPH)
+ continue;
+
+ // Total crack
+ glyphy_point_t pt;
+ pt.x = nAdvance + aPos.X() + mpGlyphOffsets[i].du;
+ pt.y = aPos.Y() + mpGlyphOffsets[i].dv;
+ glyph_info_t gi;
+ demo_font_lookup_glyph( mrWinFontEntry.mpGLyphyFont, mpOutGlyphs[i], &gi );
+ demo_shader_add_glyph_vertices( pt, font_size, &gi, &vertices, NULL );
+
+ nAdvance += pGlyphWidths[i];
+ }
+
+ GLfloat mat[16];
+ m4LoadIdentity( mat );
+
+ GLint viewport[4];
+ glGetIntegerv( GL_VIEWPORT, viewport );
+ CHECK_GL_ERROR();
+
+ double width = viewport[2];
+ double height = viewport[3];
+
+#if 0
+ // Based on demo_view_apply_transform(). I don't really understand it.
+
+ // No scaling, no translation needed here
+
+ // Perspective (but why would we want that in LO; it's needed in glyphy-demo because there
+ // you can rotate the "sheet" the text is drawn on)
+ {
+ double d = std::max( width, height );
+ double near = d / 4;
+ double far = near + d;
+ double factor = near / (2 * near + d);
+ m4Frustum( mat, -width * factor, width * factor, -height * factor, height * factor, near, far );
+ m4Translate( mat, 0, 0, -(near + d * 0.5) );
+ }
+
+ // No rotation here
+
+ // Fix 'up'
+ m4Scale( mat, 1, -1, 1 );
+
+ // Foo
+ // m4Scale( mat, 0.5, 0.5, 1 );
+
+ // The "Center buffer" part in demo_view_display()
+ m4Translate( mat, width/2, height/2, 0 );
+
+#else
+ // Crack that just happens to show something (even if not completely in correct location) in
+ // some cases, but not all. I don't understand why.
+ double scale = std::max( 2/width, 2/height );
+ m4Scale( mat, scale, -scale, 1);
+ m4Translate( mat, -width/2, -height/2, 0 );
+#endif
+
+ GLuint u_matViewProjection_loc = glGetUniformLocation( mrWinFontEntry.mnGLyphyProgram, "u_matViewProjection" );
+ CHECK_GL_ERROR();
+ glUniformMatrix4fv( u_matViewProjection_loc, 1, GL_FALSE, mat );
+ CHECK_GL_ERROR();
+
+ GLuint a_glyph_vertex_loc = glGetAttribLocation( mrWinFontEntry.mnGLyphyProgram, "a_glyph_vertex" );
+ GLuint buf_name;
+ glGenBuffers( 1, &buf_name );
+ CHECK_GL_ERROR();
+ glBindBuffer( GL_ARRAY_BUFFER, buf_name );
+ CHECK_GL_ERROR();
+
+ glBufferData( GL_ARRAY_BUFFER, sizeof(glyph_vertex_t) * vertices.size(), (const char *) &vertices[0], GL_STATIC_DRAW );
+ CHECK_GL_ERROR();
+ glEnableVertexAttribArray( a_glyph_vertex_loc );
+ CHECK_GL_ERROR();
+ glVertexAttribPointer( a_glyph_vertex_loc, 4, GL_FLOAT, GL_FALSE, sizeof(glyph_vertex_t), 0 );
+ CHECK_GL_ERROR();
+ glDrawArrays( GL_TRIANGLES, 0, vertices.size() );
+ CHECK_GL_ERROR();
+ glDisableVertexAttribArray( a_glyph_vertex_loc );
+ CHECK_GL_ERROR();
+ glDeleteBuffers( 1, &buf_name );
+ CHECK_GL_ERROR();
+ }
+ pImpl->PostDraw();
+
+ return true;
+}
+
+bool UniscribeLayout::DrawCachedGlyphsUsingTextures(SalGraphics& rGraphics) const
+{
+ WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics);
+ HDC hDC = rWinGraphics.getHDC();
+
+ Rectangle aRect;
+ GetBoundRect(rGraphics, aRect);
+
+ COLORREF color = GetTextColor(hDC);
+ SalColor salColor = MAKE_SALCOLOR(GetRValue(color), GetGValue(color), GetBValue(color));
+
+ WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(rWinGraphics.mpImpl.get());
+ if (!pImpl)
+ return false;
+
+ pImpl->PreDraw();
+
+ // FIXME: This code snippet is mostly copied from the one in
+ // UniscribeLayout::DrawTextImpl. Should be factored out.
+ int nBaseClusterOffset = 0;
+ int nBaseGlyphPos = -1;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ const VisualItem& rVisualItem = mpVisualItems[ nItem ];
+
+ // skip if there is nothing to display
+ int nMinGlyphPos, nEndGlyphPos;
+ if( !GetItemSubrange( rVisualItem, nMinGlyphPos, nEndGlyphPos ) )
+ continue;
+
+ if( nBaseGlyphPos < 0 )
+ {
+ // adjust draw position relative to cluster start
+ if( rVisualItem.IsRTL() )
+ nBaseGlyphPos = nEndGlyphPos - 1;
+ else
+ nBaseGlyphPos = nMinGlyphPos;
+
+ int i = mnMinCharPos;
+ while( (--i >= rVisualItem.mnMinCharPos)
+ && (nBaseGlyphPos == mpLogClusters[i]) )
+ nBaseClusterOffset += mpCharWidths[i];
+
+ if( !rVisualItem.IsRTL() )
+ nBaseClusterOffset = -nBaseClusterOffset;
+ }
+
+ // now draw the matching glyphs in this item
+ Point aRelPos( rVisualItem.mnXOffset + nBaseClusterOffset, 0 );
+ Point aPos = GetDrawPosition( aRelPos );
+
+ int nAdvance = 0;
+
+ // This has to be in sync with UniscribeLayout::FillDXArray(), so that
+ // actual and reported glyph positions (used for e.g. cursor caret
+ // positioning) match.
+ const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
+
+ for (int i = nMinGlyphPos; i < nEndGlyphPos; i++)
+ {
+ // Ignore dropped glyphs.
+ if (mpOutGlyphs[i] == DROPPED_OUTGLYPH)
+ continue;
+
+ assert(mrWinFontEntry.GlyphIsCached(mpOutGlyphs[i]));
+
+ const OpenGLGlyphCacheChunk& rChunk = mrWinFontEntry.GetCachedGlyphChunkFor(mpOutGlyphs[i]);
+ const int n = mpOutGlyphs[i] - rChunk.mnFirstGlyph;
+
+ if (rChunk.mbVertical)
+ {
+ SalTwoRect a2Rects(rChunk.maLocation[n].Left(), rChunk.maLocation[n].Top(),
+ rChunk.maLocation[n].getWidth(), rChunk.maLocation[n].getHeight(),
+ aPos.X(), nAdvance + aPos.Y(),
+ rChunk.maLocation[n].getWidth(), rChunk.maLocation[n].getHeight()); // ???
+ pImpl->DrawMask(*rChunk.mpTexture, salColor, a2Rects);
+ }
+ else
+ {
+ SalTwoRect a2Rects(rChunk.maLocation[n].Left(), rChunk.maLocation[n].Top(),
+ rChunk.maLocation[n].getWidth(), rChunk.maLocation[n].getHeight(),
+ nAdvance + aPos.X() + mpGlyphOffsets[i].du - rChunk.getExtraOffset(), aPos.Y() + mpGlyphOffsets[i].dv - rChunk.mnAscent - rChunk.getExtraOffset(),
+ rChunk.maLocation[n].getWidth(), rChunk.maLocation[n].getHeight()); // ???
+ pImpl->DrawMask(*rChunk.mpTexture, salColor, a2Rects);
+ }
+ nAdvance += pGlyphWidths[i];
+ }
+ }
+ pImpl->PostDraw();
+
+ return true;
+}
+
+bool UniscribeLayout::DrawCachedGlyphs(SalGraphics& rGraphics) const
+{
+ if (mbUseGLyphy)
+ return DrawCachedGlyphsUsingGLyphy(rGraphics);
+ else
+ return DrawCachedGlyphsUsingTextures( rGraphics );
+}
+
+DeviceCoordinate UniscribeLayout::FillDXArray( DeviceCoordinate* pDXArray ) const
+{
+ // calculate width of the complete layout
+ long nWidth = mnBaseAdv;
+ for( int nItem = mnItemCount; --nItem >= 0; )
+ {
+ const VisualItem& rVI = mpVisualItems[ nItem ];
+
+ // skip if there is nothing to display
+ int nMinGlyphPos, nEndGlyphPos;
+ if( !GetItemSubrange( rVI, nMinGlyphPos, nEndGlyphPos ) )
+ continue;
+
+ // width = xoffset + width of last item
+ nWidth = rVI.mnXOffset;
+ const int* pGlyphWidths = mpJustifications ? mpJustifications : mpGlyphAdvances;
+ for( int i = nMinGlyphPos; i < nEndGlyphPos; ++i )
+ nWidth += pGlyphWidths[i];
+ break;
+ }
+
+ // copy the virtual char widths into pDXArray[]
+ if( pDXArray )
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ pDXArray[ i - mnMinCharPos ] = mpCharWidths[ i ];
+
+ return nWidth;
+}
+
+sal_Int32 UniscribeLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const
+{
+ long nWidth = 0;
+ for( int i = mnMinCharPos; i < mnEndCharPos; ++i )
+ {
+ nWidth += mpCharWidths[ i ] * nFactor;
+
+ // check if the nMaxWidth still fits the current sub-layout
+ if( nWidth >= nMaxWidth )
+ {
+ // go back to cluster start
+ // we have to find the visual item first since the mpLogClusters[]
+ // needed to find the cluster start is relative to the visual item
+ int nMinGlyphIndex = 0;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ const VisualItem& rVisualItem = mpVisualItems[ nItem ];
+ nMinGlyphIndex = rVisualItem.mnMinGlyphPos;
+ if( (i >= rVisualItem.mnMinCharPos)
+ && (i < rVisualItem.mnEndCharPos) )
+ break;
+ }
+ // now go back to the matching cluster start
+ do
+ {
+ int nGlyphPos = mpLogClusters[i] + nMinGlyphIndex;
+ if( 0 != mpVisualAttrs[ nGlyphPos ].fClusterStart )
+ return i;
+ } while( --i >= mnMinCharPos );
+
+ // if the cluster starts before the start of the visual item
+ // then set the visual breakpoint before this item
+ return mnMinCharPos;
+ }
+
+ // the visual break also depends on the nCharExtra between the characters
+ nWidth += nCharExtra;
+ }
+
+ // the whole layout did fit inside the nMaxWidth
+ return -1;
+}
+
+void UniscribeLayout::GetCaretPositions( int nMaxIdx, long* pCaretXArray ) const
+{
+ int i;
+ for( i = 0; i < nMaxIdx; ++i )
+ pCaretXArray[ i ] = -1;
+ std::unique_ptr<long[]> const pGlyphPos(new long[mnGlyphCount + 1]);
+ for( i = 0; i <= mnGlyphCount; ++i )
+ pGlyphPos[ i ] = -1;
+
+ long nXPos = 0;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ const VisualItem& rVisualItem = mpVisualItems[ nItem ];
+ if( rVisualItem.IsEmpty() )
+ continue;
+
+ if (mnLayoutFlags & SalLayoutFlags::ForFallback)
+ {
+ nXPos = rVisualItem.mnXOffset;
+ }
+ // get glyph positions
+ // TODO: handle when rVisualItem's glyph range is only partially used
+ for( i = rVisualItem.mnMinGlyphPos; i < rVisualItem.mnEndGlyphPos; ++i )
+ {
+ pGlyphPos[ i ] = nXPos;
+ nXPos += mpGlyphAdvances[ i ];
+ }
+ // rightmost position of this visualitem
+ pGlyphPos[ i ] = nXPos;
+
+ // convert glyph positions to character positions
+ i = rVisualItem.mnMinCharPos;
+ if( i < mnMinCharPos )
+ i = mnMinCharPos;
+ for(; (i < rVisualItem.mnEndCharPos) && (i < mnEndCharPos); ++i )
+ {
+ int j = mpLogClusters[ i ] + rVisualItem.mnMinGlyphPos;
+ int nCurrIdx = (i - mnMinCharPos) * 2;
+ if( !rVisualItem.IsRTL() )
+ {
+ // normal positions for LTR case
+ pCaretXArray[ nCurrIdx ] = pGlyphPos[ j ];
+ pCaretXArray[ nCurrIdx+1 ] = pGlyphPos[ j+1 ];
+ }
+ else
+ {
+ // reverse positions for RTL case
+ pCaretXArray[ nCurrIdx ] = pGlyphPos[ j+1 ];
+ pCaretXArray[ nCurrIdx+1 ] = pGlyphPos[ j ];
+ }
+ }
+ }
+
+ if (!(mnLayoutFlags & SalLayoutFlags::ForFallback))
+ {
+ nXPos = 0;
+ // fixup unknown character positions to neighbor
+ for( i = 0; i < nMaxIdx; ++i )
+ {
+ if( pCaretXArray[ i ] >= 0 )
+ nXPos = pCaretXArray[ i ];
+ else
+ pCaretXArray[ i ] = nXPos;
+ }
+ }
+}
+
+void UniscribeLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+{
+ SalLayout::AdjustLayout( rArgs );
+
+ // adjust positions if requested
+ if( rArgs.mpDXArray )
+ ApplyDXArray( rArgs );
+ else if( rArgs.mnLayoutWidth )
+ Justify( rArgs.mnLayoutWidth );
+}
+
+void UniscribeLayout::ApplyDXArray( const ImplLayoutArgs& rArgs )
+{
+ const long* pDXArray = rArgs.mpDXArray;
+
+ // increase char widths in string range to desired values
+ bool bModified = false;
+ int nOldWidth = 0;
+ DBG_ASSERT( mnUnitsPerPixel==1, "UniscribeLayout.mnUnitsPerPixel != 1" );
+ int i,j;
+ for( i = mnMinCharPos, j = 0; i < mnEndCharPos; ++i, ++j )
+ {
+ int nNewCharWidth = (pDXArray[j] - nOldWidth);
+ // TODO: nNewCharWidth *= mnUnitsPerPixel;
+ if( mpCharWidths[i] != nNewCharWidth )
+ {
+ mpCharWidths[i] = nNewCharWidth;
+ bModified = true;
+ }
+ nOldWidth = pDXArray[j];
+ }
+
+ if( !bModified )
+ return;
+
+ // initialize justifications array
+ mpJustifications = new int[ mnGlyphCapacity ];
+ for( i = 0; i < mnGlyphCount; ++i )
+ mpJustifications[ i ] = mpGlyphAdvances[ i ];
+
+ // apply new widths to script items
+ long nXOffset = 0;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ VisualItem& rVisualItem = mpVisualItems[ nItem ];
+
+ // set the position of this visual item
+ rVisualItem.mnXOffset = nXOffset;
+
+ // ignore empty visual items
+ if( rVisualItem.IsEmpty() )
+ {
+ for (i = rVisualItem.mnMinCharPos; i < rVisualItem.mnEndCharPos; i++)
+ nXOffset += mpCharWidths[i];
+ continue;
+ }
+ // ignore irrelevant visual items
+ if( (rVisualItem.mnMinCharPos >= mnEndCharPos)
+ || (rVisualItem.mnEndCharPos <= mnMinCharPos) )
+ continue;
+
+ // if needed prepare special handling for arabic justification
+ rVisualItem.mbHasKashidas = false;
+ if( rVisualItem.IsRTL() )
+ {
+ for( i = rVisualItem.mnMinGlyphPos; i < rVisualItem.mnEndGlyphPos; ++i )
+ if ( (1U << mpVisualAttrs[i].uJustification) & 0xFF82 ) // any Arabic justification
+ { // excluding SCRIPT_JUSTIFY_NONE
+ // yes
+ rVisualItem.mbHasKashidas = true;
+ // so prepare for kashida handling
+ InitKashidaHandling();
+ break;
+ }
+
+ if( rVisualItem.HasKashidas() )
+ for( i = rVisualItem.mnMinGlyphPos; i < rVisualItem.mnEndGlyphPos; ++i )
+ {
+ // TODO: check if we still need this hack after correction of kashida placing?
+ // (i87688): apparently yes, we still need it!
+ if ( mpVisualAttrs[i].uJustification == SCRIPT_JUSTIFY_NONE )
+ // usp decided that justification can't be applied here
+ // but maybe our Kashida algorithm thinks differently.
+ // To avoid trouble (gaps within words, last character of
+ // a word gets a Kashida appended) override this.
+
+ // I chose SCRIPT_JUSTIFY_ARABIC_KASHIDA to replace SCRIPT_JUSTIFY_NONE
+ // just because this previous hack (which I haven't understand, sorry) used
+ // the same value to replace. Don't know if this is really the best
+ // thing to do, but it seems to fix things
+ mpVisualAttrs[i].uJustification = SCRIPT_JUSTIFY_ARABIC_KASHIDA;
+ }
+ }
+
+ // convert virtual charwidths to glyph justification values
+ HRESULT nRC = ScriptApplyLogicalWidth(
+ mpCharWidths + rVisualItem.mnMinCharPos,
+ rVisualItem.mnEndCharPos - rVisualItem.mnMinCharPos,
+ rVisualItem.mnEndGlyphPos - rVisualItem.mnMinGlyphPos,
+ mpLogClusters + rVisualItem.mnMinCharPos,
+ mpVisualAttrs + rVisualItem.mnMinGlyphPos,
+ mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
+ &rVisualItem.mpScriptItem->a,
+ &rVisualItem.maABCWidths,
+ mpJustifications + rVisualItem.mnMinGlyphPos );
+
+ if( nRC != 0 )
+ {
+ delete[] mpJustifications;
+ mpJustifications = NULL;
+ break;
+ }
+
+ // to prepare for the next visual item
+ // update nXOffset to the next items position
+ // before the mpJustifications[] array gets modified
+ int nMinGlyphPos, nEndGlyphPos;
+ if( GetItemSubrange( rVisualItem, nMinGlyphPos, nEndGlyphPos ) )
+ {
+ for( i = nMinGlyphPos; i < nEndGlyphPos; ++i )
+ nXOffset += mpJustifications[ i ];
+
+ if( rVisualItem.mbHasKashidas )
+ KashidaItemFix( nMinGlyphPos, nEndGlyphPos );
+ }
+
+ // workaround needed for older USP versions:
+ // right align the justification-adjusted glyphs in their cells for RTL-items
+ // unless the right alignment is done by inserting kashidas
+ if( bManualCellAlign && rVisualItem.IsRTL() && !rVisualItem.HasKashidas() )
+ {
+ for( i = nMinGlyphPos; i < nEndGlyphPos; ++i )
+ {
+ const int nXOffsetAdjust = mpJustifications[i] - mpGlyphAdvances[i];
+ // #i99862# skip diacritics, we mustn't add extra justification to diacritics
+ int nIdxAdd = i - 1;
+ while( (nIdxAdd >= nMinGlyphPos) && !mpGlyphAdvances[nIdxAdd] )
+ --nIdxAdd;
+ if( nIdxAdd < nMinGlyphPos )
+ rVisualItem.mnXOffset += nXOffsetAdjust;
+ else
+ mpJustifications[nIdxAdd] += nXOffsetAdjust;
+ mpJustifications[i] -= nXOffsetAdjust;
+ }
+ }
+
+ // tdf#94897: Don't add extra justification to chars with diacritics when the diacritic is a
+ // separate glyph, followed by blank, in LTR
+ if( !rVisualItem.IsRTL() )
+ {
+ for( i = nMinGlyphPos; i < nEndGlyphPos; ++i )
+ {
+ const int nXOffsetAdjust = mpJustifications[i] - mpGlyphAdvances[i];
+ if( nXOffsetAdjust == 0 )
+ continue;
+ int nIdxAdd = i + 1;
+ while( (nIdxAdd < nEndGlyphPos) && mpVisualAttrs[nIdxAdd].fDiacritic )
+ ++nIdxAdd;
+ if( nIdxAdd == i + 1 )
+ continue;
+ if( nIdxAdd >= nEndGlyphPos || mpVisualAttrs[nIdxAdd].uJustification != SCRIPT_JUSTIFY_BLANK )
+ continue;
+ mpJustifications[nIdxAdd] += nXOffsetAdjust;
+ mpJustifications[i] -= nXOffsetAdjust;
+ }
+ }
+ }
+}
+
+void UniscribeLayout::InitKashidaHandling()
+{
+ if( mnMinKashidaGlyph != 0 ) // already initialized
+ return;
+
+ mrWinFontEntry.InitKashidaHandling( mhDC );
+ mnMinKashidaWidth = static_cast<int>(mfFontScale * mrWinFontEntry.GetMinKashidaWidth());
+ mnMinKashidaGlyph = mrWinFontEntry.GetMinKashidaGlyph();
+}
+
+// adjust the kashida placement matching to the WriterEngine
+void UniscribeLayout::KashidaItemFix( int nMinGlyphPos, int nEndGlyphPos )
+{
+ // workaround needed for all known USP versions:
+ // ApplyLogicalWidth does not match ScriptJustify behaviour
+ for( int i = nMinGlyphPos; i < nEndGlyphPos; ++i )
+ {
+ // check for vowels
+ if( (i > nMinGlyphPos && !mpGlyphAdvances[ i-1 ])
+ && (1U << mpVisualAttrs[i].uJustification) & 0xFF83 ) // all Arabic justifiction types
+ { // including SCRIPT_JUSTIFY_NONE
+ // vowel, we do it like ScriptJustify does
+ // the vowel gets the extra width
+ long nSpaceAdded = mpJustifications[ i ] - mpGlyphAdvances[ i ];
+ mpJustifications [ i ] = mpGlyphAdvances [ i ];
+ mpJustifications [ i - 1 ] += nSpaceAdded;
+ }
+ }
+
+ // redistribute the widths for kashidas
+ for( int i = nMinGlyphPos; i < nEndGlyphPos; )
+ KashidaWordFix ( nMinGlyphPos, nEndGlyphPos, &i );
+}
+
+bool UniscribeLayout::KashidaWordFix ( int nMinGlyphPos, int nEndGlyphPos, int* pnCurrentPos )
+{
+ // doing pixel work within a word.
+ // sometimes we have extra pixels and sometimes we miss some pixels to get to mnMinKashidaWidth
+
+ // find the next kashida
+ int nMinPos = *pnCurrentPos;
+ int nMaxPos = *pnCurrentPos;
+ for( int i = nMaxPos; i < nEndGlyphPos; ++i )
+ {
+ if( (mpVisualAttrs[ i ].uJustification >= SCRIPT_JUSTIFY_ARABIC_BLANK)
+ && (mpVisualAttrs[ i ].uJustification < SCRIPT_JUSTIFY_ARABIC_NORMAL) )
+ break;
+ nMaxPos = i;
+ }
+ *pnCurrentPos = nMaxPos + 1;
+ if( nMinPos == nMaxPos )
+ return false;
+
+ // calculate the available space for an extra kashida
+ long nMaxAdded = 0;
+ int nKashPos = -1;
+ for( int i = nMaxPos; i >= nMinPos; --i )
+ {
+ long nSpaceAdded = mpJustifications[ i ] - mpGlyphAdvances[ i ];
+ if( nSpaceAdded > nMaxAdded )
+ {
+ nKashPos = i;
+ nMaxAdded = nSpaceAdded;
+ }
+ }
+
+ // return early if there is no need for an extra kashida
+ if ( nMaxAdded <= 0 )
+ return false;
+ // return early if there is not enough space for an extra kashida
+ if( 2*nMaxAdded < mnMinKashidaWidth )
+ return false;
+
+ // redistribute the extra spacing to the kashida position
+ for( int i = nMinPos; i <= nMaxPos; ++i )
+ {
+ if( i == nKashPos )
+ continue;
+ // everything else should not have extra spacing
+ long nSpaceAdded = mpJustifications[ i ] - mpGlyphAdvances[ i ];
+ if( nSpaceAdded > 0 )
+ {
+ mpJustifications[ i ] -= nSpaceAdded;
+ mpJustifications[ nKashPos ] += nSpaceAdded;
+ }
+ }
+
+ // check if we fulfill minimal kashida width
+ long nSpaceAdded = mpJustifications[ nKashPos ] - mpGlyphAdvances[ nKashPos ];
+ if( nSpaceAdded < mnMinKashidaWidth )
+ {
+ // ugly: steal some pixels
+ long nSteal = 1;
+ if ( nMaxPos - nMinPos > 0 && ((mnMinKashidaWidth - nSpaceAdded) > (nMaxPos - nMinPos)))
+ nSteal = (mnMinKashidaWidth - nSpaceAdded) / (nMaxPos - nMinPos);
+ for( int i = nMinPos; i <= nMaxPos; ++i )
+ {
+ if( i == nKashPos )
+ continue;
+ nSteal = std::min( mnMinKashidaWidth - nSpaceAdded, nSteal );
+ if ( nSteal > 0 )
+ {
+ mpJustifications [ i ] -= nSteal;
+ mpJustifications [ nKashPos ] += nSteal;
+ nSpaceAdded += nSteal;
+ }
+ if( nSpaceAdded >= mnMinKashidaWidth )
+ return true;
+ }
+ }
+
+ // blank padding
+ long nSpaceMissing = mnMinKashidaWidth - nSpaceAdded;
+ if( nSpaceMissing > 0 )
+ {
+ // inner glyph: distribute extra space evenly
+ if( (nMinPos > nMinGlyphPos) && (nMaxPos < nEndGlyphPos - 1) )
+ {
+ mpJustifications [ nKashPos ] += nSpaceMissing;
+ long nHalfSpace = nSpaceMissing / 2;
+ mpJustifications [ nMinPos - 1 ] -= nHalfSpace;
+ mpJustifications [ nMaxPos + 1 ] -= nSpaceMissing - nHalfSpace;
+ }
+ // rightmost: left glyph gets extra space
+ else if( nMinPos > nMinGlyphPos )
+ {
+ mpJustifications [ nMinPos - 1 ] -= nSpaceMissing;
+ mpJustifications [ nKashPos ] += nSpaceMissing;
+ }
+ // leftmost: right glyph gets extra space
+ else if( nMaxPos < nEndGlyphPos - 1 )
+ {
+ mpJustifications [ nKashPos ] += nSpaceMissing;
+ mpJustifications [ nMaxPos + 1 ] -= nSpaceMissing;
+ }
+ else
+ return false;
+ }
+
+ return true;
+}
+
+void UniscribeLayout::Justify( DeviceCoordinate nNewWidth )
+{
+ DeviceCoordinate nOldWidth = 0;
+ int i;
+ for( i = mnMinCharPos; i < mnEndCharPos; ++i )
+ nOldWidth += mpCharWidths[ i ];
+ if( nOldWidth <= 0 )
+ return;
+
+ nNewWidth *= mnUnitsPerPixel; // convert into font units
+ if( nNewWidth == nOldWidth )
+ return;
+ // prepare to distribute the extra width evenly among the visual items
+ const double fStretch = (double)nNewWidth / nOldWidth;
+
+ // initialize justifications array
+ mpJustifications = new int[ mnGlyphCapacity ];
+ for( i = 0; i < mnGlyphCapacity; ++i )
+ mpJustifications[ i ] = mpGlyphAdvances[ i ];
+
+ // justify stretched script items
+ long nXOffset = 0;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ VisualItem& rVisualItem = mpVisualItems[ nItem ];
+ if( rVisualItem.IsEmpty() )
+ continue;
+
+ if( (rVisualItem.mnMinCharPos < mnEndCharPos)
+ && (rVisualItem.mnEndCharPos > mnMinCharPos) )
+ {
+ long nItemWidth = 0;
+ for( i = rVisualItem.mnMinCharPos; i < rVisualItem.mnEndCharPos; ++i )
+ nItemWidth += mpCharWidths[ i ];
+ nItemWidth = (int)((fStretch - 1.0) * nItemWidth + 0.5);
+
+ ScriptJustify(
+ mpVisualAttrs + rVisualItem.mnMinGlyphPos,
+ mpGlyphAdvances + rVisualItem.mnMinGlyphPos,
+ rVisualItem.mnEndGlyphPos - rVisualItem.mnMinGlyphPos,
+ nItemWidth,
+ mnMinKashidaWidth,
+ mpJustifications + rVisualItem.mnMinGlyphPos );
+
+ rVisualItem.mnXOffset = nXOffset;
+ nXOffset += nItemWidth;
+ }
+ }
+}
+
+bool UniscribeLayout::IsKashidaPosValid ( int nCharPos ) const
+{
+ // we have to find the visual item first since the mpLogClusters[]
+ // needed to find the cluster start is relative to to the visual item
+ int nMinGlyphIndex = -1;
+ for( int nItem = 0; nItem < mnItemCount; ++nItem )
+ {
+ const VisualItem& rVisualItem = mpVisualItems[ nItem ];
+ if( (nCharPos >= rVisualItem.mnMinCharPos)
+ && (nCharPos < rVisualItem.mnEndCharPos) )
+ {
+ nMinGlyphIndex = rVisualItem.mnMinGlyphPos;
+ break;
+ }
+ }
+ // Invalid char pos or leftmost glyph in visual item
+ if ( nMinGlyphIndex == -1 || !mpLogClusters[ nCharPos ] )
+ return false;
+
+// This test didn't give the expected results
+/* if( mpLogClusters[ nCharPos+1 ] == mpLogClusters[ nCharPos ])
+ // two chars, one glyph
+ return false;*/
+
+ const int nGlyphPos = mpLogClusters[ nCharPos ] + nMinGlyphIndex;
+ if( nGlyphPos <= 0 )
+ return true;
+ // justification is only allowed if the glyph to the left has not SCRIPT_JUSTIFY_NONE
+ // and not SCRIPT_JUSTIFY_ARABIC_BLANK
+ // special case: glyph to the left is vowel (no advance width)
+ if ( mpVisualAttrs[ nGlyphPos-1 ].uJustification == SCRIPT_JUSTIFY_ARABIC_BLANK
+ || ( mpVisualAttrs[ nGlyphPos-1 ].uJustification == SCRIPT_JUSTIFY_NONE
+ && mpGlyphAdvances [ nGlyphPos-1 ] ))
+ return false;
+ return true;
+}
+
+#if ENABLE_GRAPHITE
+
+sal_GlyphId GraphiteLayoutWinImpl::getKashidaGlyph(int & rWidth)
+{
+ rWidth = mrFont.GetMinKashidaWidth();
+ return mrFont.GetMinKashidaGlyph();
+}
+
+float gr_fontAdvance(const void* appFontHandle, gr_uint16 glyphId)
+{
+ HDC hDC = reinterpret_cast<HDC>(const_cast<void*>(appFontHandle));
+ GLYPHMETRICS gm;
+ const MAT2 mat2 = {{0,1}, {0,0}, {0,0}, {0,1}};
+ if (GDI_ERROR == GetGlyphOutlineW(hDC, glyphId, GGO_GLYPH_INDEX | GGO_METRICS,
+ &gm, 0, NULL, &mat2))
+ {
+ return .0f;
+ }
+ return gm.gmCellIncX;
+}
+
+GraphiteWinLayout::GraphiteWinLayout(HDC hDC, const ImplWinFontData& rWFD, ImplWinFontEntry& rWFE, bool bUseOpenGL) throw()
+ : WinLayout(hDC, rWFD, rWFE, bUseOpenGL), mpFont(NULL),
+ maImpl(rWFD.GraphiteFace(), rWFE)
+{
+ // the log font size may differ from the font entry size if scaling is used for large fonts
+ LOGFONTW aLogFont;
+ GetObjectW( mhFont, sizeof(LOGFONTW), &aLogFont);
+ mpFont = gr_make_font_with_advance_fn(static_cast<float>(-aLogFont.lfHeight),
+ hDC, gr_fontAdvance, rWFD.GraphiteFace());
+ maImpl.SetFont(mpFont);
+ const OString aLang = OUStringToOString( LanguageTag::convertToBcp47( rWFE.maFontSelData.meLanguage ),
+ RTL_TEXTENCODING_ASCII_US);
+ OString name = OUStringToOString(
+ rWFE.maFontSelData.maTargetName, RTL_TEXTENCODING_UTF8 );
+ sal_Int32 nFeat = name.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + 1;
+ if (nFeat > 0)
+ {
+ OString aFeat = name.copy(nFeat, name.getLength() - nFeat);
+ mpFeatures = new grutils::GrFeatureParser(rWFD.GraphiteFace(), aFeat.getStr(), aLang.getStr());
+ }
+ else
+ {
+ mpFeatures = new grutils::GrFeatureParser(rWFD.GraphiteFace(), aLang.getStr());
+ }
+ maImpl.SetFeatures(mpFeatures);
+}
+
+GraphiteWinLayout::~GraphiteWinLayout()
+{
+ delete mpFeatures;
+ gr_font_destroy(maImpl.GetFont());
+}
+
+bool GraphiteWinLayout::LayoutText( ImplLayoutArgs & args)
+{
+ HFONT hUnRotatedFont = 0;
+ if (args.mnOrientation)
+ {
+ // Graphite gets very confused if the font is rotated
+ LOGFONTW aLogFont;
+ GetObjectW( mhFont, sizeof(LOGFONTW), &aLogFont);
+ aLogFont.lfEscapement = 0;
+ aLogFont.lfOrientation = 0;
+ hUnRotatedFont = CreateFontIndirectW( &aLogFont);
+ SelectFont(mhDC, hUnRotatedFont);
+ }
+ WinLayout::AdjustLayout(args);
+ maImpl.SetFontScale(WinLayout::mfFontScale);
+ bool bSucceeded = maImpl.LayoutText(args);
+ if (args.mnOrientation)
+ {
+ // restore the rotated font
+ SelectFont(mhDC, mhFont);
+ DeleteObject(hUnRotatedFont);
+ }
+ return bSucceeded;
+}
+
+void GraphiteWinLayout::AdjustLayout(ImplLayoutArgs& rArgs)
+{
+ WinLayout::AdjustLayout(rArgs);
+ maImpl.DrawBase() = WinLayout::maDrawBase;
+ maImpl.DrawOffset() = WinLayout::maDrawOffset;
+ if ( (rArgs.mnFlags & SalLayoutFlags::BiDiRtl) && rArgs.mpDXArray)
+ {
+ mrWinFontEntry.InitKashidaHandling(mhDC);
+ }
+ maImpl.AdjustLayout(rArgs);
+}
+
+void GraphiteWinLayout::DrawTextImpl(HDC hDC) const
+{
+ HFONT hOrigFont = DisableFontScaling();
+ maImpl.DrawBase() = WinLayout::maDrawBase;
+ maImpl.DrawOffset() = WinLayout::maDrawOffset;
+ const int MAX_GLYPHS = 2;
+ sal_GlyphId glyphIntStr[MAX_GLYPHS];
+ WORD glyphWStr[MAX_GLYPHS];
+ int glyphIndex = 0;
+ Point aPos(0,0);
+ int nGlyphs = 0;
+ do
+ {
+ nGlyphs = maImpl.GetNextGlyphs(1, glyphIntStr, aPos, glyphIndex);
+ if (nGlyphs < 1)
+ break;
+ std::copy(glyphIntStr, glyphIntStr + nGlyphs, glyphWStr);
+ ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, NULL, (LPCWSTR)&(glyphWStr), nGlyphs, NULL);
+ } while (nGlyphs);
+ if( hOrigFont )
+ DeleteFont(SelectFont(hDC, hOrigFont));
+}
+
+bool GraphiteWinLayout::CacheGlyphs(SalGraphics& /*rGraphics*/) const
+{
+ return false;
+}
+
+bool GraphiteWinLayout::DrawCachedGlyphs(SalGraphics& /*rGraphics*/) const
+{
+ return false;
+}
+
+sal_Int32 GraphiteWinLayout::GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const
+{
+ sal_Int32 nBreak = maImpl.GetTextBreak(nMaxWidth, nCharExtra, nFactor);
+ return nBreak;
+}
+
+DeviceCoordinate GraphiteWinLayout::FillDXArray( DeviceCoordinate* pDXArray ) const
+{
+ return maImpl.FillDXArray(pDXArray);
+}
+
+void GraphiteWinLayout::GetCaretPositions( int nArraySize, long* pCaretXArray ) const
+{
+ maImpl.GetCaretPositions(nArraySize, pCaretXArray);
+}
+
+int GraphiteWinLayout::GetNextGlyphs( int length, sal_GlyphId* glyph_out,
+ Point& pos_out, int& glyph_slot, DeviceCoordinate* glyph_adv, int* char_index,
+ const PhysicalFontFace** pFallbackFonts ) const
+{
+ maImpl.DrawBase() = WinLayout::maDrawBase;
+ maImpl.DrawOffset() = WinLayout::maDrawOffset;
+ return maImpl.GetNextGlyphs(length, glyph_out, pos_out, glyph_slot, glyph_adv, char_index, pFallbackFonts);
+}
+
+void GraphiteWinLayout::MoveGlyph( int glyph_idx, long new_x_pos )
+{
+ maImpl.MoveGlyph(glyph_idx, new_x_pos);
+}
+
+void GraphiteWinLayout::DropGlyph( int glyph_idx )
+{
+ maImpl.DropGlyph(glyph_idx);
+}
+
+void GraphiteWinLayout::Simplify( bool is_base )
+{
+ maImpl.Simplify(is_base);
+}
+#endif // ENABLE_GRAPHITE
+
+SalLayout* WinSalGraphics::GetTextLayout( ImplLayoutArgs& /*rArgs*/, int nFallbackLevel )
+{
+ if (!mpWinFontEntry[nFallbackLevel]) return nullptr;
+
+ assert(mpWinFontData[nFallbackLevel]);
+
+ WinLayout* pWinLayout = NULL;
+
+ const ImplWinFontData& rFontFace = *mpWinFontData[ nFallbackLevel ];
+ ImplWinFontEntry& rFontInstance = *mpWinFontEntry[ nFallbackLevel ];
+
+ bool bUseOpenGL = OpenGLHelper::isVCLOpenGLEnabled() && !mbPrinter;
+
+ if (!bUspInited)
+ InitUSP();
+#if ENABLE_GRAPHITE
+ if (rFontFace.SupportsGraphite())
+ {
+ pWinLayout = new GraphiteWinLayout(getHDC(), rFontFace, rFontInstance, bUseOpenGL);
+ }
+ else
+#endif // ENABLE_GRAPHITE
+ {
+ pWinLayout = new UniscribeLayout(getHDC(), rFontFace, rFontInstance, bUseOpenGL);
+ // NOTE: it must be guaranteed that the WinSalGraphics lives longer than
+ // the created UniscribeLayout, otherwise the data passed into the
+ // constructor might become invalid too early
+ }
+
+ if( mfFontScale[nFallbackLevel] != 1.0 )
+ pWinLayout->SetFontScale( mfFontScale[nFallbackLevel] );
+
+ return pWinLayout;
+}
+
+int WinSalGraphics::GetMinKashidaWidth()
+{
+ if( !mpWinFontEntry[0] )
+ return 0;
+ mpWinFontEntry[0]->InitKashidaHandling( getHDC() );
+ int nMinKashida = static_cast<int>(mfFontScale[0] * mpWinFontEntry[0]->GetMinKashidaWidth());
+ return nMinKashida;
+}
+
+ImplWinFontEntry::ImplWinFontEntry( FontSelectPattern& rFSD )
+: ImplFontEntry( rFSD )
+, mpGLyphyAtlas( nullptr )
+, mpGLyphyFont( nullptr )
+, mnMinKashidaWidth( -1 )
+, mnMinKashidaGlyph( -1 )
+{
+ maScriptCache = NULL;
+ mpGLyphyFont = NULL;
+ mbGLyphySetupCalled = false;
+}
+
+ImplWinFontEntry::~ImplWinFontEntry()
+{
+ if( maScriptCache != NULL )
+ ScriptFreeCache( &maScriptCache );
+}
+
+bool ImplWinFontEntry::InitKashidaHandling( HDC hDC )
+{
+ if( mnMinKashidaWidth >= 0 ) // already cached?
+ return mnMinKashidaWidth;
+
+ // initialize the kashida width
+ mnMinKashidaWidth = 0;
+ mnMinKashidaGlyph = 0;
+ if (!bUspInited)
+ InitUSP();
+
+ SCRIPT_FONTPROPERTIES aFontProperties;
+ aFontProperties.cBytes = sizeof (aFontProperties);
+ SCRIPT_CACHE& rScriptCache = GetScriptCache();
+ HRESULT nRC = ScriptGetFontProperties( hDC, &rScriptCache, &aFontProperties );
+ if( nRC != 0 )
+ return false;
+ mnMinKashidaWidth = aFontProperties.iKashidaWidth;
+ mnMinKashidaGlyph = aFontProperties.wgKashida;
+
+ return true;
+}
+
+PhysicalFontFace* ImplWinFontData::Clone() const
+{
+#if ENABLE_GRAPHITE
+ if ( mpGraphiteData )
+ mpGraphiteData->AddReference();
+#endif
+ PhysicalFontFace* pClone = new ImplWinFontData( *this );
+ return pClone;
+}
+
+ImplFontEntry* ImplWinFontData::CreateFontInstance( FontSelectPattern& rFSD ) const
+{
+ ImplFontEntry* pEntry = new ImplWinFontEntry( rFSD );
+ return pEntry;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */