/* -*- 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 <memory>
#include <osl/module.h>
#include <osl/file.h>
#include <sal/log.hxx>
#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 <win/DWriteTextRenderer.hxx>
#include <sft.hxx>
#include <sallayout.hxx>
#include <win/ScopedHDC.hxx>
#include <cstdio>
#include <cstdlib>
#include <rtl/character.hxx>
#include <algorithm>
#include <shlwapi.h>
#include <winver.h>
GlobalGlyphCache * GlobalGlyphCache::get() {
SalData * data = GetSalData();
if (!data->m_pGlobalGlyphCache) {
data->m_pGlobalGlyphCache.reset(new GlobalGlyphCache);
}
return data->m_pGlobalGlyphCache.get();
}
bool WinFontInstance::CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex, SalGraphics& rGraphics)
{
OpenGLGlyphDrawElement aElement;
ScopedHDC aHDC(CreateCompatibleDC(hDC));
if (!aHDC)
{
SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
return false;
}
HFONT hOrigFont = static_cast<HFONT>(SelectObject(aHDC.get(), hFont));
if (hOrigFont == nullptr)
{
SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
return false;
}
// For now we assume DWrite is present and we won't bother with fallback paths.
D2DWriteTextOutRenderer * pTxt = dynamic_cast<D2DWriteTextOutRenderer *>(&TextOutRenderer::get(true));
if (!pTxt)
return false;
pTxt->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased);
if (!pTxt->BindFont(aHDC.get()))
{
SAL_WARN("vcl.gdi", "Binding of font failed. The font might not be supported by DirectWrite.");
return false;
}
std::vector<WORD> aGlyphIndices(1);
aGlyphIndices[0] = nGlyphIndex;
// Fetch the ink boxes and calculate the size of the atlas.
tools::Rectangle bounds(0, 0, 0, 0);
auto aInkBoxes = pTxt->GetGlyphInkBoxes(aGlyphIndices.data(), aGlyphIndices.data() + 1);
if (aInkBoxes.empty())
return false;
for (auto &box : aInkBoxes)
bounds.Union(box + Point(bounds.Right(), 0));
// bounds.Top() is the offset from the baseline at (0,0) to the top of the
// inkbox.
aElement.mnBaselineOffset = -bounds.Top();
aElement.mnHeight = bounds.getHeight();
aElement.mbVertical = false;
// 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 between glyphs will help.
std::vector<float> aGlyphAdv(1); // offsets between glyphs
std::vector<DWRITE_GLYPH_OFFSET> aGlyphOffset(1, DWRITE_GLYPH_OFFSET{0.0f, 0.0f});
std::vector<int> aEnds(1); // end of each glyph box
float totWidth = 0;
{
int overhang = aInkBoxes[0].Left();
int blackWidth = aInkBoxes[0].getWidth(); // width of non-AA pixels
aElement.maLeftOverhangs = overhang;
aGlyphAdv[0] = blackWidth + aElement.getExtraSpace();
aGlyphOffset[0].advanceOffset = -overhang;
totWidth += aGlyphAdv[0];
aEnds[0] = totWidth;
}
// Leave extra space also at top and bottom
int nBitmapWidth = totWidth;
int nBitmapHeight = bounds.getHeight() + aElement.getExtraSpace();
UINT nPos = 0;
// FIXME: really I don't get why 'vertical' makes any difference [!] what does it mean !?
if (aElement.mbVertical)
{
aElement.maLocation.SetLeft(0);
aElement.maLocation.SetRight(nBitmapWidth);
aElement.maLocation.SetTop(nPos);
aElement.maLocation.SetBottom( nPos + aGlyphAdv[0] + aElement.maLeftOverhangs );
}
else
{
aElement.maLocation.SetLeft(nPos);
aElement.maLocation.SetRight(aEnds[0]);
aElement.maLocation.SetTop(0);
aElement.maLocation.SetBottom( bounds.getHeight() + aElement.getExtraSpace() );
}
nPos = aEnds[0];
OpenGLCompatibleDC aDC(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight);
SetTextColor(aDC.getCompatibleHDC(), RGB(0, 0, 0));
SetBkColor(aDC.getCompatibleHDC(), RGB(255, 255, 255));
aDC.fill(RGB(0xff, 0xff, 0xff));
pTxt->BindDC(aDC.getCompatibleHDC(), tools::Rectangle(0, 0, nBitmapWidth, nBitmapHeight));
auto pRT = pTxt->GetRenderTarget();
ID2D1SolidColorBrush* pBrush = nullptr;
if (!SUCCEEDED(pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBrush)))
{
pTxt->ReleaseFont();
return false;
}
D2D1_POINT_2F baseline = {
static_cast<FLOAT>(aElement.getExtraOffset()),
static_cast<FLOAT>(aElement.getExtraOffset() + aElement.mnBaselineOffset)
};
DWRITE_GLYPH_RUN glyphs = {
pTxt->GetFontFace(),
pTxt->GetEmHeight(),
1,
aGlyphIndices.data(),
aGlyphAdv.data(),
aGlyphOffset.data(),
false,
0
};
pRT->BeginDraw();
pRT->DrawGlyphRun(baseline, &glyphs, pBrush);
HRESULT hResult = pRT->EndDraw();
pBrush->Release();
switch (hResult)
{
case S_OK:
break;
case D2DERR_RECREATE_TARGET:
pTxt->CreateRenderTarget();
break;
default:
SAL_WARN("vcl.gdi", "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
SelectFont(aDC.getCompatibleHDC(), hOrigFont);
return false;
}
pTxt->ReleaseFont();
if (!GlyphCache::ReserveTextureSpace(aElement, nBitmapWidth, nBitmapHeight))
return false;
if (!aDC.copyToTexture(aElement.maTexture))
return false;
maGlyphCache.PutDrawElementInCache(aElement, nGlyphIndex);
SelectFont(aDC.getCompatibleHDC(), hOrigFont);
return true;
}
TextOutRenderer & TextOutRenderer::get(bool bUseDWrite)
{
SalData *const pSalData = GetSalData();
if (!pSalData)
{ // don't call this after DeInitVCL()
fprintf(stderr, "TextOutRenderer fatal error: no SalData");
abort();
}
if (bUseDWrite)
{
static bool const bSuccess(D2DWriteTextOutRenderer::InitModules());
if (bSuccess && !pSalData->m_pD2DWriteTextOutRenderer)
{
pSalData->m_pD2DWriteTextOutRenderer.reset(new D2DWriteTextOutRenderer());
}
if (pSalData->m_pD2DWriteTextOutRenderer)
{
return *pSalData->m_pD2DWriteTextOutRenderer;
}
// else: fall back to GDI
}
if (!pSalData->m_pExTextOutRenderer)
{
pSalData->m_pExTextOutRenderer.reset(new ExTextOutRenderer);
}
return *pSalData->m_pExTextOutRenderer;
}
bool ExTextOutRenderer::operator ()(GenericSalLayout const &rLayout,
SalGraphics & /*rGraphics*/,
HDC hDC)
{
HFONT hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT ));
HFONT hAltFont = nullptr;
bool bUseAltFont = false;
bool bShift = false;
if (rLayout.GetFont().GetFontSelectPattern().mbVertical)
{
LOGFONTW aLogFont;
GetObjectW(hFont, sizeof(aLogFont), &aLogFont);
if (aLogFont.lfFaceName[0] == '@')
{
memmove(&aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1],
sizeof(aLogFont.lfFaceName)-sizeof(aLogFont.lfFaceName[0]));
hAltFont = CreateFontIndirectW(&aLogFont);
}
else
{
bShift = true;
aLogFont.lfEscapement += 2700;
aLogFont.lfOrientation = aLogFont.lfEscapement;
hAltFont = CreateFontIndirectW(&aLogFont);
}
}
UINT nTextAlign = GetTextAlign ( hDC );
int nStart = 0;
Point aPos(0, 0);
const GlyphItem* pGlyph;
while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
{
WORD glyphWStr[] = { pGlyph->maGlyphId };
if (hAltFont && pGlyph->IsVertical() == bUseAltFont)
{
bUseAltFont = !bUseAltFont;
SelectFont(hDC, bUseAltFont ? hAltFont : hFont);
}
if (bShift && pGlyph->IsVertical())
SetTextAlign(hDC, TA_TOP|TA_LEFT);
ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, nullptr, LPCWSTR(&glyphWStr), 1, nullptr);
if (bShift && pGlyph->IsVertical())
SetTextAlign(hDC, nTextAlign);
}
if (hAltFont)
{
if (bUseAltFont)
SelectFont(hDC, hFont);
DeleteObject(hAltFont);
}
return true;
}
std::unique_ptr<SalLayout> WinSalGraphics::GetTextLayout(ImplLayoutArgs& /*rArgs*/, int nFallbackLevel)
{
if (!mpWinFontEntry[nFallbackLevel])
return nullptr;
assert(mpWinFontEntry[nFallbackLevel]->GetFontFace());
mpWinFontEntry[nFallbackLevel]->SetGraphics(this);
GenericSalLayout *aLayout = new GenericSalLayout(*mpWinFontEntry[nFallbackLevel]);
return std::unique_ptr<SalLayout>(aLayout);
}
WinFontInstance::WinFontInstance(const PhysicalFontFace& rPFF, const FontSelectPattern& rFSP)
: LogicalFontInstance(rPFF, rFSP)
, m_pGraphics(nullptr)
, m_hFont(nullptr)
, m_fScale(1.0f)
{
}
WinFontInstance::~WinFontInstance()
{
if (m_hFont)
::DeleteFont(m_hFont);
}
bool WinFontInstance::hasHScale() const
{
const FontSelectPattern &rPattern = GetFontSelectPattern();
int nHeight(rPattern.mnHeight);
int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
return nWidth != nHeight;
}
static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
{
sal_uLong nLength = 0;
unsigned char* pBuffer = nullptr;
WinFontInstance* pFont = static_cast<WinFontInstance*>(pUserData);
HDC hDC = pFont->GetGraphics()->getHDC();
HFONT hFont = pFont->GetHFONT();
assert(hDC);
assert(hFont);
HGDIOBJ hOrigFont = SelectObject(hDC, hFont);
nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0);
if (nLength > 0 && nLength != GDI_ERROR)
{
pBuffer = new unsigned char[nLength];
::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength);
}
SelectObject(hDC, hOrigFont);
hb_blob_t* pBlob = nullptr;
if (pBuffer != nullptr)
pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
return pBlob;
}
hb_font_t* WinFontInstance::ImplInitHbFont()
{
assert(m_pGraphics);
hb_font_t* pHbFont = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
// Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale().
if (GetFontSelectPattern().mnWidth)
{
double nUPEM = hb_face_get_upem(hb_font_get_face(pHbFont));
LOGFONTW aLogFont;
GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont);
// Set the height (font size) to EM to minimize rounding errors.
aLogFont.lfHeight = -nUPEM;
// Set width to the default to get the original value in the metrics.
aLogFont.lfWidth = 0;
// Get the font metrics.
HDC hDC = m_pGraphics->getHDC();
HFONT hNewFont = CreateFontIndirectW(&aLogFont);
HGDIOBJ hOrigFont = SelectObject(hDC, hNewFont);
TEXTMETRICW aFontMetric;
GetTextMetricsW(hDC, &aFontMetric);
SelectObject(hDC, hOrigFont);
DeleteObject(hNewFont);
SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth);
}
return pHbFont;
}
void WinFontInstance::SetGraphics(WinSalGraphics *pGraphics)
{
m_pGraphics = pGraphics;
if (m_hFont)
return;
HFONT hOrigFont;
m_hFont = m_pGraphics->ImplDoSetFont(GetFontSelectPattern(), GetFontFace(), m_fScale, hOrigFont);
SelectObject(m_pGraphics->getHDC(), hOrigFont);
}
bool WinSalGraphics::CacheGlyphs(const GenericSalLayout& rLayout)
{
static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == nullptr);
if (!bDoGlyphCaching)
return false;
HDC hDC = getHDC();
WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
HFONT hFONT = rFont.GetHFONT();
int nStart = 0;
Point aPos(0, 0);
const GlyphItem* pGlyph;
while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
{
if (!rFont.GetGlyphCache().IsGlyphCached(pGlyph->maGlyphId))
{
if (!rFont.CacheGlyphToAtlas(hDC, hFONT, pGlyph->maGlyphId, *this))
return false;
}
}
return true;
}
bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout& rLayout)
{
HDC hDC = getHDC();
tools::Rectangle aRect;
rLayout.GetBoundRect(*this, aRect);
COLORREF color = GetTextColor(hDC);
Color salColor = Color(GetRValue(color), GetGValue(color), GetBValue(color));
WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(mpImpl.get());
if (!pImpl)
return false;
WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
int nStart = 0;
Point aPos(0, 0);
const GlyphItem* pGlyph;
while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
{
OpenGLGlyphDrawElement& rElement(rFont.GetGlyphCache().GetDrawElement(pGlyph->maGlyphId));
OpenGLTexture& rTexture = rElement.maTexture;
if (!rTexture)
return false;
SalTwoRect a2Rects(0, 0,
rTexture.GetWidth(), rTexture.GetHeight(),
aPos.X() - rElement.getExtraOffset() + rElement.maLeftOverhangs,
aPos.Y() - rElement.mnBaselineOffset - rElement.getExtraOffset(),
rTexture.GetWidth(), rTexture.GetHeight());
pImpl->DeferredTextDraw(rTexture, salColor, a2Rects);
}
return true;
}
void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite)
{
TextOutRenderer &render = TextOutRenderer::get(bUseDWrite);
render(rLayout, *this, hDC);
}
void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
{
HDC hDC = getHDC();
const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
const HFONT hLayoutFont = pWinFont->GetHFONT();
// Our DirectWrite renderer is incomplete, skip it for non-horizontal or
// stretched text.
bool bForceGDI = rLayout.GetOrientation() || pWinFont->hasHScale();
bool bUseOpenGL = OpenGLHelper::isVCLOpenGLEnabled() && !mbPrinter;
if (!bUseOpenGL)
{
// no OpenGL, just classic rendering
const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont);
DrawTextLayout(rLayout, hDC, false);
::SelectFont(hDC, hOrigFont);
}
// if we can't draw the cached OpenGL glyphs, try to draw a full OpenGL layout
else if (bForceGDI || !CacheGlyphs(rLayout) || !DrawCachedGlyphs(rLayout))
{
// 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.
tools::Rectangle aRect;
rLayout.GetBoundRect(*this, aRect);
WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(mpImpl.get());
if (pImpl)
{
pImpl->PreDraw();
OpenGLCompatibleDC aDC(*this, 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);
RECT aWinRect = { aRect.Left(), aRect.Top(), aRect.Left() + aRect.GetWidth(), aRect.Top() + aRect.GetHeight() };
::FillRect(aDC.getCompatibleHDC(), &aWinRect, static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
// setup the hidden DC with black color and white background, we will
// use the result of the text drawing later as a mask only
const HFONT hOrigFont = ::SelectFont(aDC.getCompatibleHDC(), hLayoutFont);
::SetTextColor(aDC.getCompatibleHDC(), RGB(0, 0, 0));
::SetBkColor(aDC.getCompatibleHDC(), RGB(255, 255, 255));
UINT nTextAlign = ::GetTextAlign(hDC);
::SetTextAlign(aDC.getCompatibleHDC(), nTextAlign);
COLORREF color = ::GetTextColor(hDC);
Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
// the actual drawing
DrawTextLayout(rLayout, aDC.getCompatibleHDC(), !bForceGDI);
std::unique_ptr<OpenGLTexture> xTexture(aDC.getTexture());
if (xTexture)
pImpl->DrawMask(*xTexture, salColor, aDC.getTwoRect());
::SelectFont(aDC.getCompatibleHDC(), hOrigFont);
pImpl->PostDraw();
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression 'aElement.mbVertical' is always false.