diff options
Diffstat (limited to 'vcl/unx')
-rw-r--r-- | vcl/unx/generic/gdi/cairotextrender.cxx | 2 | ||||
-rw-r--r-- | vcl/unx/generic/gdi/gcach_xpeer.hxx | 2 | ||||
-rw-r--r-- | vcl/unx/generic/glyphs/gcach_layout.cxx | 621 | ||||
-rw-r--r-- | vcl/unx/generic/glyphs/glyphcache.cxx | 379 | ||||
-rw-r--r-- | vcl/unx/generic/glyphs/graphite_serverfont.cxx | 135 | ||||
-rw-r--r-- | vcl/unx/generic/glyphs/scrptrun.cxx | 234 | ||||
-rw-r--r-- | vcl/unx/generic/glyphs/scrptrun.h | 173 |
7 files changed, 1544 insertions, 2 deletions
diff --git a/vcl/unx/generic/gdi/cairotextrender.cxx b/vcl/unx/generic/gdi/cairotextrender.cxx index 97af78fc5800..5e39d0e162c5 100644 --- a/vcl/unx/generic/gdi/cairotextrender.cxx +++ b/vcl/unx/generic/gdi/cairotextrender.cxx @@ -28,7 +28,7 @@ #include "generic/printergfx.hxx" #include "generic/genpspgraphics.h" #include "generic/geninst.h" -#include "generic/glyphcache.hxx" +#include "unx/glyphcache.hxx" #include "PhysicalFontFace.hxx" #include "impfont.hxx" diff --git a/vcl/unx/generic/gdi/gcach_xpeer.hxx b/vcl/unx/generic/gdi/gcach_xpeer.hxx index 5d3b89ffe3f4..d933c8b61490 100644 --- a/vcl/unx/generic/gdi/gcach_xpeer.hxx +++ b/vcl/unx/generic/gdi/gcach_xpeer.hxx @@ -20,7 +20,7 @@ #ifndef INCLUDED_VCL_UNX_GENERIC_GDI_GCACH_XPEER_HXX #define INCLUDED_VCL_UNX_GENERIC_GDI_GCACH_XPEER_HXX -#include "generic/glyphcache.hxx" +#include "unx/glyphcache.hxx" class X11GlyphCache : public GlyphCache { diff --git a/vcl/unx/generic/glyphs/gcach_layout.cxx b/vcl/unx/generic/glyphs/gcach_layout.cxx new file mode 100644 index 000000000000..70ac1901f09d --- /dev/null +++ b/vcl/unx/generic/glyphs/gcach_layout.cxx @@ -0,0 +1,621 @@ +/* -*- 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 "unx/freetype_glyphcache.hxx" +#include <sallayout.hxx> +#include <salgdi.hxx> +#include <scrptrun.h> + +#include <i18nlangtag/mslangid.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/unohelp.hxx> + +#include <rtl/instance.hxx> + +#include <hb-icu.h> +#include <hb-ot.h> + +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> + +#ifndef HB_VERSION_ATLEAST +#define HB_VERSION_ATLEAST(a,b,c) 0 +#endif + +// layout implementation for ServerFont +ServerFontLayout::ServerFontLayout( ServerFont& rFont ) +: mrServerFont( rFont ) +{ +} + +void ServerFontLayout::DrawText( SalGraphics& rSalGraphics ) const +{ + rSalGraphics.DrawServerFontLayout( *this ); +} + +bool ServerFontLayout::LayoutText( ImplLayoutArgs& rArgs ) +{ + return mrServerFont.GetLayoutEngine()->Layout(*this, rArgs); +} + +void ServerFontLayout::AdjustLayout( ImplLayoutArgs& rArgs ) +{ + GenericSalLayout::AdjustLayout( rArgs ); + + // apply asian kerning if the glyphs are not already formatted + if( (rArgs.mnFlags & SalLayoutFlags::KerningAsian) + && !(rArgs.mnFlags & SalLayoutFlags::Vertical) ) + if( (rArgs.mpDXArray != nullptr) || (rArgs.mnLayoutWidth != 0) ) + ApplyAsianKerning(rArgs.mrStr); + + // insert kashidas where requested by the formatting array + if( (rArgs.mnFlags & SalLayoutFlags::KashidaJustification) && rArgs.mpDXArray ) + { + int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex ); + KashidaJustify( nKashidaIndex, rGM.GetCharWidth() ); + // TODO: kashida-GSUB/GPOS + } + } +} + +void ServerFontLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos, + bool bRightToLeft) +{ + if (nCharPos < 0) + return; + + using namespace ::com::sun::star; + + if (!mxBreak.is()) + mxBreak = vcl::unohelper::CreateBreakIterator(); + + lang::Locale aLocale(rArgs.maLanguageTag.getLocale()); + + //if position nCharPos is missing in the font, grab the entire grapheme and + //mark all glyphs as missing so the whole thing is rendered with the same + //font + sal_Int32 nDone; + sal_Int32 nGraphemeStartPos = + mxBreak->previousCharacters(rArgs.mrStr, nCharPos+1, aLocale, + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + sal_Int32 nGraphemeEndPos = + mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale, + i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + + rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft); +} + +std::ostream &operator <<(std::ostream& s, ServerFont* pFont) +{ +#ifndef SAL_LOG_INFO + (void) pFont; +#else + FT_Face aFace = pFont->GetFtFace(); + const char* pName = FT_Get_Postscript_Name(aFace); + if (pName) + s << pName; + else + s << pFont->GetFontFileName(); +#endif + return s; +} + +static hb_blob_t *getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData) +{ + char pTagName[5]; + pTagName[0] = (char)(nTableTag >> 24); + pTagName[1] = (char)(nTableTag >> 16); + pTagName[2] = (char)(nTableTag >> 8); + pTagName[3] = (char)(nTableTag); + pTagName[4] = 0; + + ServerFont* pFont = static_cast<ServerFont*>(pUserData); + + SAL_INFO("vcl.harfbuzz", "getFontTable(" << pFont << ", " << pTagName << ")"); + + sal_uLong nLength; + const unsigned char* pBuffer = pFont->GetTable(pTagName, &nLength); + + hb_blob_t* pBlob = nullptr; + if (pBuffer != nullptr) + pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY, const_cast<unsigned char *>(pBuffer), nullptr); + + return pBlob; +} + +static hb_bool_t getFontGlyph(hb_font_t* /*font*/, void* pFontData, + hb_codepoint_t ch, hb_codepoint_t vs, + hb_codepoint_t* nGlyphIndex, + void* /*pUserData*/) +{ + ServerFont* pFont = static_cast<ServerFont*>(pFontData); + *nGlyphIndex = pFont->GetRawGlyphIndex(ch, vs); + + // tdf#89231 if the font is missing non-breaking space, then use a normal space + if (*nGlyphIndex == 0 && ch == 0x202F && !vs) + *nGlyphIndex = pFont->GetRawGlyphIndex(' '); + + return *nGlyphIndex != 0; +} + +static hb_position_t getGlyphAdvanceH(hb_font_t* /*font*/, void* pFontData, + hb_codepoint_t nGlyphIndex, + void* /*pUserData*/) +{ + ServerFont* pFont = static_cast<ServerFont*>(pFontData); + const GlyphMetric& rGM = pFont->GetGlyphMetric(nGlyphIndex); + return rGM.GetCharWidth() << 6; +} + +#if !HB_VERSION_ATLEAST(1, 1, 2) +static hb_position_t getGlyphAdvanceV(hb_font_t* /*font*/, void* /*pFontData*/, + hb_codepoint_t /*nGlyphIndex*/, + void* /*pUserData*/) +{ + // XXX: vertical metrics + return 0; +} + +static hb_bool_t getGlyphOriginH(hb_font_t* /*font*/, void* /*pFontData*/, + hb_codepoint_t /*nGlyphIndex*/, + hb_position_t* /*x*/, hb_position_t* /*y*/, + void* /*pUserData*/) +{ + // the horizontal origin is always (0, 0) + return true; +} + +static hb_bool_t getGlyphOriginV(hb_font_t* /*font*/, void* /*pFontData*/, + hb_codepoint_t /*nGlyphIndex*/, + hb_position_t* /*x*/, hb_position_t* /*y*/, + void* /*pUserData*/) +{ + // XXX: vertical origin + return true; +} +#endif + +static hb_position_t getGlyphKerningH(hb_font_t* /*font*/, void* pFontData, + hb_codepoint_t nGlyphIndex1, hb_codepoint_t nGlyphIndex2, + void* /*pUserData*/) +{ + // This callback is for old style 'kern' table, GPOS kerning is handled by HarfBuzz directly + + ServerFont* pFont = static_cast<ServerFont*>(pFontData); + FT_Face aFace = pFont->GetFtFace(); + + SAL_INFO("vcl.harfbuzz", "getGlyphKerningH(" << pFont << ", " << nGlyphIndex1 << ", " << nGlyphIndex2 << ")"); + + FT_Error error; + FT_Vector kerning; + hb_position_t ret; + + error = FT_Get_Kerning(aFace, nGlyphIndex1, nGlyphIndex2, FT_KERNING_DEFAULT, &kerning); + if (error) + ret = 0; + else + ret = kerning.x; + + return ret; +} + +#if !HB_VERSION_ATLEAST(1, 1, 2) +static hb_position_t getGlyphKerningV(hb_font_t* /*font*/, void* /*pFontData*/, + hb_codepoint_t /*nGlyphIndex1*/, hb_codepoint_t /*nGlyphIndex2*/, + void* /*pUserData*/) +{ + // XXX vertical kerning + return 0; +} +#endif + +static hb_bool_t getGlyphExtents(hb_font_t* /*font*/, void* pFontData, + hb_codepoint_t nGlyphIndex, + hb_glyph_extents_t* pExtents, + void* /*pUserData*/) +{ + ServerFont* pFont = static_cast<ServerFont*>(pFontData); + FT_Face aFace = pFont->GetFtFace(); + + SAL_INFO("vcl.harfbuzz", "getGlyphExtents(" << pFont << ", " << nGlyphIndex << ")"); + + FT_Error error; + error = FT_Load_Glyph(aFace, nGlyphIndex, FT_LOAD_DEFAULT); + if (!error) + { + pExtents->x_bearing = aFace->glyph->metrics.horiBearingX; + pExtents->y_bearing = aFace->glyph->metrics.horiBearingY; + pExtents->width = aFace->glyph->metrics.width; + pExtents->height = -aFace->glyph->metrics.height; + } + + return !error; +} + +static hb_bool_t getGlyphContourPoint(hb_font_t* /*font*/, void* pFontData, + hb_codepoint_t nGlyphIndex, unsigned int nPointIndex, + hb_position_t *x, hb_position_t *y, + void* /*pUserData*/) +{ + bool ret = false; + ServerFont* pFont = static_cast<ServerFont*>(pFontData); + FT_Face aFace = pFont->GetFtFace(); + + SAL_INFO("vcl.harfbuzz", "getGlyphContourPoint(" << pFont << ", " << nGlyphIndex << ", " << nPointIndex << ")"); + + FT_Error error; + error = FT_Load_Glyph(aFace, nGlyphIndex, FT_LOAD_DEFAULT); + if (!error) + { + if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) + { + if (nPointIndex < (unsigned int) aFace->glyph->outline.n_points) + { + *x = aFace->glyph->outline.points[nPointIndex].x; + *y = aFace->glyph->outline.points[nPointIndex].y; + ret = true; + } + } + } + + return ret; +} + +static hb_font_funcs_t* getFontFuncs() +{ + static hb_font_funcs_t* funcs = hb_font_funcs_create(); + + hb_font_funcs_set_glyph_func (funcs, getFontGlyph, nullptr, nullptr); + hb_font_funcs_set_glyph_h_advance_func (funcs, getGlyphAdvanceH, nullptr, nullptr); + hb_font_funcs_set_glyph_h_kerning_func (funcs, getGlyphKerningH, nullptr, nullptr); + hb_font_funcs_set_glyph_extents_func (funcs, getGlyphExtents, nullptr, nullptr); + hb_font_funcs_set_glyph_contour_point_func (funcs, getGlyphContourPoint, nullptr, nullptr); +#if !HB_VERSION_ATLEAST(1, 1, 2) + hb_font_funcs_set_glyph_v_advance_func (funcs, getGlyphAdvanceV, nullptr, nullptr); + hb_font_funcs_set_glyph_h_origin_func (funcs, getGlyphOriginH, nullptr, nullptr); + hb_font_funcs_set_glyph_v_origin_func (funcs, getGlyphOriginV, nullptr, nullptr); + hb_font_funcs_set_glyph_v_kerning_func (funcs, getGlyphKerningV, nullptr, nullptr); +#endif + + return funcs; +} + +#if !HB_VERSION_ATLEAST(1, 1, 0) +// Disabled Unicode compatibility decomposition, see fdo#66715 +static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/, + hb_codepoint_t /*u*/, + hb_codepoint_t* /*decomposed*/, + void* /*user_data*/) +{ + return 0; +} + +static hb_unicode_funcs_t* getUnicodeFuncs() +{ + static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs()); + hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr); + return ufuncs; +} +#endif + +class HbLayoutEngine : public ServerFontLayoutEngine +{ +private: + hb_script_t maHbScript; + hb_face_t* mpHbFace; + int mnUnitsPerEM; + +public: + explicit HbLayoutEngine(ServerFont&); + virtual ~HbLayoutEngine(); + + virtual bool Layout(ServerFontLayout&, ImplLayoutArgs&) override; +}; + +HbLayoutEngine::HbLayoutEngine(ServerFont& rServerFont) +: maHbScript(HB_SCRIPT_INVALID), + mpHbFace(nullptr), + mnUnitsPerEM(0) +{ + FT_Face aFtFace = rServerFont.GetFtFace(); + mnUnitsPerEM = rServerFont.GetEmUnits(); + + mpHbFace = hb_face_create_for_tables(getFontTable, &rServerFont, nullptr); + hb_face_set_index(mpHbFace, aFtFace->face_index); + hb_face_set_upem(mpHbFace, mnUnitsPerEM); +} + +HbLayoutEngine::~HbLayoutEngine() +{ + hb_face_destroy(mpHbFace); +} + +struct HbScriptRun +{ + int32_t mnMin; + int32_t mnEnd; + hb_script_t maScript; + + HbScriptRun(int32_t nMin, int32_t nEnd, UScriptCode aScript) + : mnMin(nMin), mnEnd(nEnd), + maScript(hb_icu_script_to_script(aScript)) + {} +}; + +typedef std::vector<HbScriptRun> HbScriptRuns; + +namespace vcl { + struct Run + { + int32_t nStart; + int32_t nEnd; + UScriptCode nCode; + Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_) + : nStart(nStart_), nEnd(nEnd_), nCode(nCode_) + {} + }; + + class TextLayoutCache + { + public: + std::vector<vcl::Run> runs; + TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd) + { + vcl::ScriptRun aScriptRun( + reinterpret_cast<const UChar *>(pStr), + nEnd); + while (aScriptRun.next()) + { + runs.push_back(Run(aScriptRun.getScriptStart(), + aScriptRun.getScriptEnd(), aScriptRun.getScriptCode())); + } + } + }; +} + +std::shared_ptr<vcl::TextLayoutCache> ServerFontLayout::CreateTextLayoutCache( + OUString const& rString) const +{ + return std::make_shared<vcl::TextLayoutCache>(rString.getStr(), rString.getLength()); +} + +bool HbLayoutEngine::Layout(ServerFontLayout& rLayout, ImplLayoutArgs& rArgs) +{ + ServerFont& rFont = rLayout.GetServerFont(); + FT_Face aFtFace = rFont.GetFtFace(); + + SAL_INFO("vcl.harfbuzz", "layout(" << this << ",rArgs=" << rArgs << ")"); + + static hb_font_funcs_t* pHbFontFuncs = getFontFuncs(); + + hb_font_t *pHbFont = hb_font_create(mpHbFace); + hb_font_set_funcs(pHbFont, pHbFontFuncs, &rFont, nullptr); + hb_font_set_scale(pHbFont, + ((uint64_t) aFtFace->size->metrics.x_scale * (uint64_t) mnUnitsPerEM) >> 16, + ((uint64_t) aFtFace->size->metrics.y_scale * (uint64_t) mnUnitsPerEM) >> 16); + hb_font_set_ppem(pHbFont, aFtFace->size->metrics.x_ppem, aFtFace->size->metrics.y_ppem); + + // allocate temporary arrays, note: round to even + int nGlyphCapacity = (3 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos) | 15) + 1; + int32_t nVirtAdv = int32_t(aFtFace->size->metrics.height*rFont.GetStretch())>>6; + + rLayout.Reserve(nGlyphCapacity); + + const int nLength = rArgs.mrStr.getLength(); + const sal_Unicode *pStr = rArgs.mrStr.getStr(); + + std::unique_ptr<vcl::TextLayoutCache> pNewScriptRun; + vcl::TextLayoutCache const* pTextLayout; + if (rArgs.m_pTextLayoutCache) + { + pTextLayout = rArgs.m_pTextLayoutCache; // use cache! + } + else + { + pNewScriptRun.reset(new vcl::TextLayoutCache(pStr, rArgs.mnEndCharPos)); + pTextLayout = pNewScriptRun.get(); + } + + Point aCurrPos(0, 0); + while (true) + { + int nBidiMinRunPos, nBidiEndRunPos; + bool bRightToLeft; + if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft)) + break; + + // Find script subruns. + int nCurrentPos = nBidiMinRunPos; + HbScriptRuns aScriptSubRuns; + size_t k = 0; + for (; k < pTextLayout->runs.size(); ++k) + { + vcl::Run const& rRun(pTextLayout->runs[k]); + if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd) + { + break; + } + } + + while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size()) + { + int32_t nMinRunPos = nCurrentPos; + int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos); + HbScriptRun aRun(nMinRunPos, nEndRunPos, pTextLayout->runs[k].nCode); + aScriptSubRuns.push_back(aRun); + + nCurrentPos = nEndRunPos; + ++k; + } + + // RTL subruns should be reversed to ensure that final glyph order is + // correct. + if (bRightToLeft) + std::reverse(aScriptSubRuns.begin(), aScriptSubRuns.end()); + + for (HbScriptRuns::iterator it = aScriptSubRuns.begin(); it != aScriptSubRuns.end(); ++it) + { + int nMinRunPos = it->mnMin; + int nEndRunPos = it->mnEnd; + int nRunLen = nEndRunPos - nMinRunPos; + maHbScript = it->maScript; + // hb_language_from_string() accept ISO639-3 language tag except for Chinese. + LanguageTag &rTag = rArgs.maLanguageTag; + OString sLanguage = OUStringToOString( MsLangId::isChinese(rTag.getLanguageType()) ? rTag.getBcp47():rTag.getLanguage() , RTL_TEXTENCODING_UTF8 ); + + int nHbFlags = HB_BUFFER_FLAGS_DEFAULT; + if (nMinRunPos == 0) + nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */ + if (nEndRunPos == nLength) + nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */ + + hb_buffer_t *pHbBuffer = hb_buffer_create(); +#if !HB_VERSION_ATLEAST(1, 1, 0) + static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs(); + hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs); +#endif + hb_buffer_set_direction(pHbBuffer, bRightToLeft ? HB_DIRECTION_RTL: HB_DIRECTION_LTR); + hb_buffer_set_script(pHbBuffer, maHbScript); + hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1)); + hb_buffer_set_flags(pHbBuffer, (hb_buffer_flags_t) nHbFlags); + hb_buffer_add_utf16( + pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength, + nMinRunPos, nRunLen); + hb_shape(pHbFont, pHbBuffer, nullptr, 0); + + int nRunGlyphCount = hb_buffer_get_length(pHbBuffer); + hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr); + hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr); + + for (int i = 0; i < nRunGlyphCount; ++i) { + int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint; + int32_t nCharPos = pHbGlyphInfos[i].cluster; + + // if needed request glyph fallback by updating LayoutArgs + if (!nGlyphIndex) + { + rLayout.SetNeedFallback(rArgs, nCharPos, bRightToLeft); + if (SalLayoutFlags::ForFallback & rArgs.mnFlags) + continue; + } + + // apply vertical flags and glyph substitution + // XXX: Use HB_DIRECTION_TTB above and apply whatever flags magic + // FixupGlyphIndex() is doing, minus the GSUB part. + if (nCharPos >= 0) + { + sal_UCS4 aChar = rArgs.mrStr[nCharPos]; + nGlyphIndex = rFont.FixupGlyphIndex(nGlyphIndex, aChar); + } + + bool bInCluster = false; + if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster) + bInCluster = true; + + long nGlyphFlags = 0; + if (bRightToLeft) + nGlyphFlags |= GlyphItem::IS_RTL_GLYPH; + + if (bInCluster) + nGlyphFlags |= GlyphItem::IS_IN_CLUSTER; + + // The whole IS_DIACRITIC concept is a stupid hack that was + // introduced ages ago to work around the utter brokenness of the + // way justification adjustments are applied (the DXArray fiasco). + // Since it is such a stupid hack, there is no sane way to directly + // map to concepts of the "outside" world, so we do some rather + // ugly hacks: + // * If the font has a GDEF table, we check for glyphs with mark + // glyph class which is sensible, except that some fonts + // (fdo#70968) assign mark class to spacing marks (which is wrong + // but usually harmless), so we try to sniff what HarfBuzz thinks + // about this glyph by checking if it gives it a zero advance + // width. + // * If the font has no GDEF table, we just check if the glyph has + // zero advance width, but this is stupid and can be wrong. A + // better way would to check the character's Unicode combining + // class, but unfortunately glyph gives combining marks the + // cluster value of its base character, so nCharPos will be + // pointing to the wrong character (but HarfBuzz might change + // this in the future). + bool bDiacritic = false; + if (hb_ot_layout_has_glyph_classes(mpHbFace)) + { + // the font has GDEF table + bool bMark = hb_ot_layout_get_glyph_class(mpHbFace, nGlyphIndex) == HB_OT_LAYOUT_GLYPH_CLASS_MARK; + if (bMark && pHbPositions[i].x_advance == 0) + bDiacritic = true; + } + else + { + // the font lacks GDEF table + if (pHbPositions[i].x_advance == 0) + bDiacritic = true; + } + + if (bDiacritic) + nGlyphFlags |= GlyphItem::IS_DIACRITIC; + + int32_t nXOffset = pHbPositions[i].x_offset >> 6; + int32_t nYOffset = pHbPositions[i].y_offset >> 6; + int32_t nXAdvance = pHbPositions[i].x_advance >> 6; + int32_t nYAdvance = pHbPositions[i].y_advance >> 6; + if ( nGlyphIndex & GF_ROTMASK ) + nXAdvance = nVirtAdv; + + Point aNewPos = Point(aCurrPos.X() + nXOffset, -(aCurrPos.Y() + nYOffset)); + const GlyphItem aGI(nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nXAdvance, nXOffset, nYOffset); + rLayout.AppendGlyph(aGI); + + aCurrPos.X() += nXAdvance; + aCurrPos.Y() += nYAdvance; + } + + hb_buffer_destroy(pHbBuffer); + } + } + + hb_font_destroy(pHbFont); + + // sort glyphs in visual order + // and then in logical order (e.g. diacritics after cluster start) + // XXX: why? + rLayout.SortGlyphItems(); + + // determine need for kashida justification + if((rArgs.mpDXArray || rArgs.mnLayoutWidth) + && ((maHbScript == HB_SCRIPT_ARABIC) || (maHbScript == HB_SCRIPT_SYRIAC))) + rArgs.mnFlags |= SalLayoutFlags::KashidaJustification; + + return true; +} + +ServerFontLayoutEngine* ServerFont::GetLayoutEngine() +{ + // find best layout engine for font, platform, script and language + if (!mpLayoutEngine) { + mpLayoutEngine = new HbLayoutEngine(*this); + } + return mpLayoutEngine; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/glyphs/glyphcache.cxx b/vcl/unx/generic/glyphs/glyphcache.cxx new file mode 100644 index 000000000000..33f06df2faab --- /dev/null +++ b/vcl/unx/generic/glyphs/glyphcache.cxx @@ -0,0 +1,379 @@ +/* -*- 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 <stdlib.h> +#include <math.h> +#include "unx/freetype_glyphcache.hxx" + +#include <vcl/svapp.hxx> +#include <vcl/bitmap.hxx> +#include <fontinstance.hxx> +#include <fontattributes.hxx> + +#include <config_graphite.h> +#if ENABLE_GRAPHITE +#include <graphite_features.hxx> +#endif + +#include <rtl/ustring.hxx> +#include <osl/file.hxx> +#include <tools/debug.hxx> + +static GlyphCache* pInstance = nullptr; + +GlyphCache::GlyphCache() +: mnMaxSize( 1500000 ), + mnBytesUsed(sizeof(GlyphCache)), + mnLruIndex(0), + mnGlyphCount(0), + mpCurrentGCFont(nullptr), + mpFtManager(nullptr) +{ + pInstance = this; + mpFtManager = new FreetypeManager; +} + +GlyphCache::~GlyphCache() +{ + InvalidateAllGlyphs(); + delete mpFtManager; +} + +void GlyphCache::InvalidateAllGlyphs() +{ + for( FontList::iterator it = maFontList.begin(), end = maFontList.end(); it != end; ++it ) + { + ServerFont* pServerFont = it->second; + // free all pServerFont related data + pServerFont->GarbageCollect( mnLruIndex+0x10000000 ); + delete pServerFont; + } + + maFontList.clear(); + mpCurrentGCFont = nullptr; +} + +inline +size_t GlyphCache::IFSD_Hash::operator()( const FontSelectPattern& rFontSelData ) const +{ + // TODO: is it worth to improve this hash function? + sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>( rFontSelData.mpFontData ); +#if ENABLE_GRAPHITE + if (rFontSelData.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + != -1) + { + OString aFeatName = OUStringToOString( rFontSelData.maTargetName, RTL_TEXTENCODING_UTF8 ); + nFontId ^= aFeatName.hashCode(); + } +#endif + size_t nHash = nFontId << 8; + nHash += rFontSelData.mnHeight; + nHash += rFontSelData.mnOrientation; + nHash += size_t(rFontSelData.mbVertical); + nHash += rFontSelData.GetSlantType(); + nHash += rFontSelData.GetWeight(); +#if ENABLE_GRAPHITE + nHash += rFontSelData.meLanguage; +#endif + return nHash; +} + +bool GlyphCache::IFSD_Equal::operator()( const FontSelectPattern& rA, const FontSelectPattern& rB) const +{ + // check font ids + sal_IntPtr nFontIdA = reinterpret_cast<sal_IntPtr>( rA.mpFontData ); + sal_IntPtr nFontIdB = reinterpret_cast<sal_IntPtr>( rB.mpFontData ); + if( nFontIdA != nFontIdB ) + return false; + + // compare with the requested metrics + if( (rA.mnHeight != rB.mnHeight) + || (rA.mnOrientation != rB.mnOrientation) + || (rA.mbVertical != rB.mbVertical) + || (rA.mbNonAntialiased != rB.mbNonAntialiased) ) + return false; + + if( (rA.GetSlantType() != rB.GetSlantType()) + || (rA.GetWeight() != rB.GetWeight()) ) + return false; + + // NOTE: ignoring meFamily deliberately + + // compare with the requested width, allow default width + int nAWidth = rA.mnWidth != 0 ? rA.mnWidth : rA.mnHeight; + int nBWidth = rB.mnWidth != 0 ? rB.mnWidth : rB.mnHeight; + if( nAWidth != nBWidth ) + return false; + +#if ENABLE_GRAPHITE + if (rA.meLanguage != rB.meLanguage) + return false; + // check for features + if ((rA.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + != -1 || + rB.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + != -1) && rA.maTargetName != rB.maTargetName) + return false; +#endif + + if (rA.mbEmbolden != rB.mbEmbolden) + return false; + + if (rA.maItalicMatrix != rB.maItalicMatrix) + return false; + + return true; +} + +GlyphCache& GlyphCache::GetInstance() +{ + return *pInstance; +} + +void GlyphCache::AddFontFile( const OString& rNormalizedName, int nFaceNum, + sal_IntPtr nFontId, const FontAttributes& rDFA) +{ + if( mpFtManager ) + mpFtManager->AddFontFile( rNormalizedName, nFaceNum, nFontId, rDFA); +} + +void GlyphCache::AnnounceFonts( PhysicalFontCollection* pFontCollection ) const +{ + if( mpFtManager ) + mpFtManager->AnnounceFonts( pFontCollection ); +} + +void GlyphCache::ClearFontCache() +{ + InvalidateAllGlyphs(); + if (mpFtManager) + mpFtManager->ClearFontList(); +} + +ServerFont* GlyphCache::CacheFont( const FontSelectPattern& rFontSelData ) +{ + // a serverfont request has pFontData + if( rFontSelData.mpFontData == nullptr ) + return nullptr; + // a serverfont request has a fontid > 0 + sal_IntPtr nFontId = rFontSelData.mpFontData->GetFontId(); + if( nFontId <= 0 ) + return nullptr; + + // the FontList's key mpFontData member is reinterpreted as font id + FontSelectPattern aFontSelData = rFontSelData; + aFontSelData.mpFontData = reinterpret_cast<PhysicalFontFace*>( nFontId ); + FontList::iterator it = maFontList.find( aFontSelData ); + if( it != maFontList.end() ) + { + ServerFont* pFound = it->second; + if( pFound ) + pFound->AddRef(); + return pFound; + } + + // font not cached yet => create new font item + ServerFont* pNew = nullptr; + if( mpFtManager ) + pNew = mpFtManager->CreateFont( aFontSelData ); + + if( pNew ) + { + maFontList[ aFontSelData ] = pNew; + mnBytesUsed += pNew->GetByteCount(); + + // enable garbage collection for new font + if( !mpCurrentGCFont ) + { + mpCurrentGCFont = pNew; + pNew->mpNextGCFont = pNew; + pNew->mpPrevGCFont = pNew; + } + else + { + pNew->mpNextGCFont = mpCurrentGCFont; + pNew->mpPrevGCFont = mpCurrentGCFont->mpPrevGCFont; + pNew->mpPrevGCFont->mpNextGCFont = pNew; + mpCurrentGCFont->mpPrevGCFont = pNew; + } + } + + return pNew; +} + +void GlyphCache::UncacheFont( ServerFont& rServerFont ) +{ + if( (rServerFont.Release() <= 0) && (mnMaxSize <= mnBytesUsed) ) + { + mpCurrentGCFont = &rServerFont; + GarbageCollect(); + } +} + +void GlyphCache::GarbageCollect() +{ + // when current GC font has been destroyed get another one + if( !mpCurrentGCFont ) + { + FontList::iterator it = maFontList.begin(); + if( it != maFontList.end() ) + mpCurrentGCFont = it->second; + } + + // unless there is no other font to collect + if( !mpCurrentGCFont ) + return; + + // prepare advance to next font for garbage collection + ServerFont* const pServerFont = mpCurrentGCFont; + mpCurrentGCFont = pServerFont->mpNextGCFont; + + if( (pServerFont == mpCurrentGCFont) // no other fonts + || (pServerFont->GetRefCount() > 0) ) // font still used + { + // try to garbage collect at least a few bytes + pServerFont->GarbageCollect( mnLruIndex - mnGlyphCount/2 ); + } + else // current GC font is unreferenced + { + DBG_ASSERT( (pServerFont->GetRefCount() == 0), + "GlyphCache::GC detected RefCount underflow" ); + + // free all pServerFont related data + pServerFont->GarbageCollect( mnLruIndex+0x10000000 ); + if( pServerFont == mpCurrentGCFont ) + mpCurrentGCFont = nullptr; + const FontSelectPattern& rIFSD = pServerFont->GetFontSelData(); + maFontList.erase( rIFSD ); + mnBytesUsed -= pServerFont->GetByteCount(); + + // remove font from list of garbage collected fonts + if( pServerFont->mpPrevGCFont ) + pServerFont->mpPrevGCFont->mpNextGCFont = pServerFont->mpNextGCFont; + if( pServerFont->mpNextGCFont ) + pServerFont->mpNextGCFont->mpPrevGCFont = pServerFont->mpPrevGCFont; + if( pServerFont == mpCurrentGCFont ) + mpCurrentGCFont = nullptr; + + delete pServerFont; + } +} + +inline void GlyphCache::UsingGlyph( ServerFont&, GlyphData& rGlyphData ) +{ + rGlyphData.SetLruValue( mnLruIndex++ ); +} + +inline void GlyphCache::AddedGlyph( ServerFont& rServerFont, GlyphData& rGlyphData ) +{ + ++mnGlyphCount; + mnBytesUsed += sizeof( rGlyphData ); + UsingGlyph( rServerFont, rGlyphData ); + GrowNotify(); +} + +void GlyphCache::GrowNotify() +{ + if( mnBytesUsed > mnMaxSize ) + GarbageCollect(); +} + +inline void GlyphCache::RemovingGlyph() +{ + mnBytesUsed -= sizeof( GlyphData ); + --mnGlyphCount; +} + +void ServerFont::ReleaseFromGarbageCollect() +{ + // remove from GC list + ServerFont* pPrev = mpPrevGCFont; + ServerFont* pNext = mpNextGCFont; + if( pPrev ) pPrev->mpNextGCFont = pNext; + if( pNext ) pNext->mpPrevGCFont = pPrev; + mpPrevGCFont = nullptr; + mpNextGCFont = nullptr; +} + +long ServerFont::Release() const +{ + DBG_ASSERT( mnRefCount > 0, "ServerFont: RefCount underflow" ); + return --mnRefCount; +} + +GlyphData& ServerFont::GetGlyphData( sal_GlyphId aGlyphId ) +{ + // usually the GlyphData is cached + GlyphList::iterator it = maGlyphList.find( aGlyphId ); + if( it != maGlyphList.end() ) { + GlyphData& rGlyphData = it->second; + GlyphCache::GetInstance().UsingGlyph( *this, rGlyphData ); + return rGlyphData; + } + + // sometimes not => we need to create and initialize it ourselves + GlyphData& rGlyphData = maGlyphList[ aGlyphId ]; + mnBytesUsed += sizeof( GlyphData ); + InitGlyphData( aGlyphId, rGlyphData ); + GlyphCache::GetInstance().AddedGlyph( *this, rGlyphData ); + return rGlyphData; +} + +void ServerFont::GarbageCollect( long nMinLruIndex ) +{ + GlyphList::iterator it = maGlyphList.begin(); + while( it != maGlyphList.end() ) + { + GlyphData& rGD = it->second; + if( (nMinLruIndex - rGD.GetLruValue()) > 0 ) + { + OSL_ASSERT( mnBytesUsed >= sizeof(GlyphData) ); + mnBytesUsed -= sizeof( GlyphData ); + GlyphCache::GetInstance().RemovingGlyph(); + it = maGlyphList.erase( it ); + } + else + ++it; + } +} + +ServerFontInstance::ServerFontInstance( FontSelectPattern& rFSD ) +: LogicalFontInstance( rFSD ) +, mpServerFont( nullptr ) +, mbGotFontOptions( false ) +{} + +void ServerFontInstance::SetServerFont(ServerFont* p) +{ + if (p == mpServerFont) + return; + if (mpServerFont) + mpServerFont->Release(); + mpServerFont = p; + if (mpServerFont) + mpServerFont->AddRef(); +} + +ServerFontInstance::~ServerFontInstance() +{ + // TODO: remove the ServerFont here instead of in the GlyphCache + if (mpServerFont) + mpServerFont->Release(); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/glyphs/graphite_serverfont.cxx b/vcl/unx/generic/glyphs/graphite_serverfont.cxx new file mode 100644 index 000000000000..d2126b05f377 --- /dev/null +++ b/vcl/unx/generic/glyphs/graphite_serverfont.cxx @@ -0,0 +1,135 @@ +/* -*- 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 . + */ + +// Header files + +// Platform +#include <i18nlangtag/languagetag.hxx> +#include <sallayout.hxx> +// Module +#include "unx/freetype_glyphcache.hxx" +#include "unx/glyphcache.hxx" +#include <graphite_features.hxx> +#include <graphite_serverfont.hxx> + +float freetypeServerFontAdvance(const void* appFontHandle, gr_uint16 glyphId) +{ + ServerFont * pServerFont = + const_cast<ServerFont*> + (static_cast<const ServerFont*>(appFontHandle)); + if (pServerFont) + { + return static_cast<float>(pServerFont->GetGlyphMetric(glyphId).GetCharWidth()); + } + return .0f; +} + +// An implementation of the GraphiteLayout interface to enable Graphite enabled fonts to be used. + +GraphiteServerFontLayout::GraphiteServerFontLayout(ServerFont& rServerFont) throw() + : ServerFontLayout(rServerFont), + maImpl(rServerFont.GetGraphiteFace()->face(), rServerFont) + , mpFeatures(nullptr) +{ + gr_font * pFont = rServerFont.GetGraphiteFace()->font(rServerFont.GetFontSelData().mnHeight, rServerFont.NeedsArtificialBold(), rServerFont.NeedsArtificialItalic()); + if (!pFont) + { + pFont = gr_make_font_with_advance_fn( + // need to use mnHeight here, mfExactHeight can give wrong values + static_cast<float>(rServerFont.GetFontSelData().mnHeight), + &rServerFont, + freetypeServerFontAdvance, + rServerFont.GetGraphiteFace()->face()); + rServerFont.GetGraphiteFace()->addFont(rServerFont.GetFontSelData().mnHeight, pFont, rServerFont.NeedsArtificialBold(), rServerFont.NeedsArtificialItalic()); + } + maImpl.SetFont(pFont); + OString aLang(""); + if (rServerFont.GetFontSelData().meLanguage != LANGUAGE_DONTKNOW) + { + aLang = OUStringToOString( LanguageTag( rServerFont.GetFontSelData().meLanguage ).getBcp47(), + RTL_TEXTENCODING_UTF8 ); + } + OString name = OUStringToOString( + rServerFont.GetFontSelData().maTargetName, RTL_TEXTENCODING_UTF8 ); +#ifdef DEBUG + printf("GraphiteServerFontLayout %lx %s size %d %f\n", (long unsigned int)this, name.getStr(), + rServerFont.GetMetricsFT().x_ppem, + rServerFont.GetFontSelData().mfExactHeight); +#endif + 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( + rServerFont.GetGraphiteFace()->face(), aFeat, aLang); +#ifdef DEBUG + if (mpFeatures) + printf("GraphiteServerFontLayout %s/%s/%s %x language %d features %d errors\n", + OUStringToOString( rServerFont.GetFontSelData().GetFamilyName(), + RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( rServerFont.GetFontSelData().maTargetName, + RTL_TEXTENCODING_UTF8 ).getStr(), + OUStringToOString( rServerFont.GetFontSelData().maSearchName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rServerFont.GetFontSelData().meLanguage, + (int)mpFeatures->numFeatures(), mpFeatures->parseErrors()); +#endif + } + else + { + mpFeatures = new grutils::GrFeatureParser( + rServerFont.GetGraphiteFace()->face(), aLang); + } + maImpl.SetFeatures(mpFeatures); +} + +GraphiteServerFontLayout::~GraphiteServerFontLayout() throw() +{ + delete mpFeatures; + mpFeatures = nullptr; +} + +bool GraphiteServerFontLayout::IsGraphiteEnabledFont(ServerFont& rServerFont) +{ + if (rServerFont.GetGraphiteFace()) + { +#ifdef DEBUG + printf("IsGraphiteEnabledFont\n"); +#endif + return true; + } + return false; +} + +sal_GlyphId GraphiteLayoutImpl::getKashidaGlyph(int & width) +{ + int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex ); + width = rGM.GetCharWidth(); + } + else + { + width = 0; + } + return nKashidaIndex; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/glyphs/scrptrun.cxx b/vcl/unx/generic/glyphs/scrptrun.cxx new file mode 100644 index 000000000000..d6557d79a894 --- /dev/null +++ b/vcl/unx/generic/glyphs/scrptrun.cxx @@ -0,0 +1,234 @@ +/* + ******************************************************************************* + * + * Copyright (c) 1995-2013 International Business Machines Corporation and others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN + * NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE + * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall not be + * used in advertising or otherwise to promote the sale, use or other dealings in + * this Software without prior written authorization of the copyright holder. + * + ******************************************************************************* + * file name: scrptrun.cpp + * + * created on: 10/17/2001 + * created by: Eric R. Mader + */ +/** + * This file is largely copied from the ICU project, + * under folder source/extra/scrptrun/scrptrun.cpp + */ +#include "unicode/utypes.h" +#include "unicode/uscript.h" + +#include "scrptrun.h" +#include <algorithm> + +namespace { + +struct PairIndices +{ + int8_t ma00[0xff]; + int8_t ma20[0x7f]; + int8_t ma30[0x7f]; + + PairIndices() + { + std::fill_n(ma00, 0xff, -1); + std::fill_n(ma20, 0x7f, -1); + std::fill_n(ma30, 0x7f, -1); + + // characters in the range 0x0000 - 0x007e (inclusive) + // ascii paired punctuation + ma00[0x28] = 0; + ma00[0x29] = 1; + ma00[0x3c] = 2; + ma00[0x3e] = 3; + ma00[0x5b] = 4; + ma00[0x5d] = 5; + ma00[0x7b] = 6; + ma00[0x7d] = 7; + // guillemets + ma00[0xab] = 8; + ma00[0xbb] = 9; + + // characters in the range 0x2000 - 0x207e (inclusive) + // general punctuation + ma20[0x18] = 10; + ma20[0x19] = 11; + ma20[0x1c] = 12; + ma20[0x1d] = 13; + ma20[0x39] = 14; + ma20[0x3a] = 15; + + // characters in the range 0x3000 - 0x307e (inclusive) + // chinese paired punctuation + ma30[0x08] = 16; + ma30[0x09] = 17; + ma30[0x0a] = 18; + ma30[0x0b] = 19; + ma30[0x0c] = 20; + ma30[0x0d] = 21; + ma30[0x0e] = 22; + ma30[0x0f] = 23; + ma30[0x10] = 24; + ma30[0x11] = 25; + ma30[0x14] = 26; + ma30[0x15] = 27; + ma30[0x16] = 28; + ma30[0x17] = 29; + ma30[0x18] = 30; + ma30[0x19] = 31; + ma30[0x1a] = 32; + ma30[0x1b] = 33; + } + + inline int32_t getPairIndex(UChar32 ch) const + { + if (ch < 0xff) + return ma00[ch]; + if (ch >= 0x2000 && ch < 0x207f) + return ma20[ch - 0x2000]; + if (ch >= 0x3000 && ch < 0x307f) + return ma30[ch - 0x3000]; + return -1; + } + +}; + +} + +static const PairIndices gPairIndices; + + +namespace vcl { + +const char ScriptRun::fgClassID=0; + +static inline UBool sameScript(int32_t scriptOne, int32_t scriptTwo) +{ + return scriptOne <= USCRIPT_INHERITED || scriptTwo <= USCRIPT_INHERITED || scriptOne == scriptTwo; +} + +UBool ScriptRun::next() +{ + int32_t startSP = parenSP; // used to find the first new open character + UErrorCode error = U_ZERO_ERROR; + + // if we've fallen off the end of the text, we're done + if (scriptEnd >= charLimit) { + return false; + } + + scriptCode = USCRIPT_COMMON; + + for (scriptStart = scriptEnd; scriptEnd < charLimit; scriptEnd += 1) { + UChar high = charArray[scriptEnd]; + UChar32 ch = high; + + // if the character is a high surrogate and it's not the last one + // in the text, see if it's followed by a low surrogate + if (high >= 0xD800 && high <= 0xDBFF && scriptEnd < charLimit - 1) + { + UChar low = charArray[scriptEnd + 1]; + + // if it is followed by a low surrogate, + // consume it and form the full character + if (low >= 0xDC00 && low <= 0xDFFF) { + ch = (high - 0xD800) * 0x0400 + low - 0xDC00 + 0x10000; + scriptEnd += 1; + } + } + + UScriptCode sc = uscript_getScript(ch, &error); + int32_t pairIndex = gPairIndices.getPairIndex(ch); + + // Paired character handling: + + // if it's an open character, push it onto the stack. + // if it's a close character, find the matching open on the + // stack, and use that script code. Any non-matching open + // characters above it on the stack will be poped. + if (pairIndex >= 0) { + if ((pairIndex & 1) == 0) { + ++parenSP; + int32_t nVecSize = parenStack.size(); + if (parenSP == nVecSize) + parenStack.resize(nVecSize + 128); + parenStack[parenSP].pairIndex = pairIndex; + parenStack[parenSP].scriptCode = scriptCode; + } else if (parenSP >= 0) { + int32_t pi = pairIndex & ~1; + + while (parenSP >= 0 && parenStack[parenSP].pairIndex != pi) { + parenSP -= 1; + } + + if (parenSP < startSP) { + startSP = parenSP; + } + + if (parenSP >= 0) { + sc = parenStack[parenSP].scriptCode; + } + } + } + + if (sameScript(scriptCode, sc)) { + if (scriptCode <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) { + scriptCode = sc; + + // now that we have a final script code, fix any open + // characters we pushed before we knew the script code. + while (startSP < parenSP) { + parenStack[++startSP].scriptCode = scriptCode; + } + } + + // if this character is a close paired character, + // pop it from the stack + if (pairIndex >= 0 && (pairIndex & 1) != 0 && parenSP >= 0) { + parenSP -= 1; + /* decrement startSP only if it is >= 0, + decrementing it unnecessarily will lead to memory corruption + while processing the above while block. + e.g. startSP = -4 , parenSP = -1 + */ + if (startSP >= 0) { + startSP -= 1; + } + } + } else { + // if the run broke on a surrogate pair, + // end it before the high surrogate + if (ch >= 0x10000) { + scriptEnd -= 1; + } + + break; + } + } + + return true; +} + +} diff --git a/vcl/unx/generic/glyphs/scrptrun.h b/vcl/unx/generic/glyphs/scrptrun.h new file mode 100644 index 000000000000..2efcff4110ba --- /dev/null +++ b/vcl/unx/generic/glyphs/scrptrun.h @@ -0,0 +1,173 @@ +/* + ******************************************************************************* + * + * Copyright (c) 1995-2013 International Business Machines Corporation and others + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN + * NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE + * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall not be + * used in advertising or otherwise to promote the sale, use or other dealings in + * this Software without prior written authorization of the copyright holder. + * + ******************************************************************************* + * file name: scrptrun.h + * + * created on: 10/17/2001 + * created by: Eric R. Mader + */ + +#ifndef INCLUDED_VCL_GENERIC_GLYPHS_SCRPTRUN_H +#define INCLUDED_VCL_GENERIC_GLYPHS_SCRPTRUN_H + +#include <sal/config.h> + +#include <sal/types.h> +#include "unicode/utypes.h" +#include "unicode/uobject.h" +#include "unicode/uscript.h" +#include <vector> + +namespace vcl { + +struct ParenStackEntry +{ + int32_t pairIndex; + UScriptCode scriptCode; + ParenStackEntry() + : pairIndex(0) + , scriptCode(USCRIPT_INVALID_CODE) + { + } +}; + +class ScriptRun : public UObject { +public: + ScriptRun(); + + ScriptRun(const UChar chars[], int32_t length); + + ScriptRun(const UChar chars[], int32_t start, int32_t length); + + void reset(); + + void reset(int32_t start, int32_t count); + + void reset(const UChar chars[], int32_t start, int32_t length); + + int32_t getScriptStart(); + + int32_t getScriptEnd(); + + UScriptCode getScriptCode(); + + UBool next(); + + /** +s * ICU "poor man's RTTI", returns a UClassID for the actual class. + * + * @stable ICU 2.2 + */ + virtual inline UClassID getDynamicClassID() const override { return getStaticClassID(); } + + /** + * ICU "poor man's RTTI", returns a UClassID for this class. + * + * @stable ICU 2.2 + */ + static inline UClassID getStaticClassID() { return static_cast<UClassID>(const_cast<char *>(&fgClassID)); } + +private: + + int32_t charStart; + int32_t charLimit; + const UChar *charArray; + + int32_t scriptStart; + int32_t scriptEnd; + UScriptCode scriptCode; + + std::vector<ParenStackEntry> parenStack; + int32_t parenSP; + + /** + * The address of this static class variable serves as this class's ID + * for ICU "poor man's RTTI". + */ + static const char fgClassID; +}; + +inline ScriptRun::ScriptRun() +{ + reset(NULL, 0, 0); +} + +inline ScriptRun::ScriptRun(const UChar chars[], int32_t length) +{ + reset(chars, 0, length); +} + +inline ScriptRun::ScriptRun(const UChar chars[], int32_t start, int32_t length) +{ + reset(chars, start, length); +} + +inline int32_t ScriptRun::getScriptStart() +{ + return scriptStart; +} + +inline int32_t ScriptRun::getScriptEnd() +{ + return scriptEnd; +} + +inline UScriptCode ScriptRun::getScriptCode() +{ + return scriptCode; +} + +inline void ScriptRun::reset() +{ + scriptStart = charStart; + scriptEnd = charStart; + scriptCode = USCRIPT_INVALID_CODE; + parenSP = -1; + parenStack.resize(128); +} + +inline void ScriptRun::reset(int32_t start, int32_t length) +{ + charStart = start; + charLimit = start + length; + + reset(); +} + +inline void ScriptRun::reset(const UChar chars[], int32_t start, int32_t length) +{ + charArray = chars; + + reset(start, length); +} + +} + +#endif |