From db8d099d4d58d9181dd670c64e97c91145b9ad4a Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 15 Mar 2025 13:53:36 -0600 Subject: [PATCH] [dwrite] Split shape code into its own file --- src/harfbuzz.cc | 1 + src/hb-coretext.hh | 2 +- src/hb-directwrite-shape.cc | 622 ++++++++++++++++++++++++++++ src/hb-directwrite.cc | 798 +----------------------------------- src/hb-directwrite.hh | 243 +++++++++++ src/meson.build | 2 +- 6 files changed, 872 insertions(+), 796 deletions(-) create mode 100644 src/hb-directwrite-shape.cc create mode 100644 src/hb-directwrite.hh diff --git a/src/harfbuzz.cc b/src/harfbuzz.cc index de45f59a5..d8f4f5cbb 100644 --- a/src/harfbuzz.cc +++ b/src/harfbuzz.cc @@ -9,6 +9,7 @@ #include "hb-coretext-font.cc" #include "hb-coretext-shape.cc" #include "hb-coretext.cc" +#include "hb-directwrite-shape.cc" #include "hb-directwrite.cc" #include "hb-draw.cc" #include "hb-face-builder.cc" diff --git a/src/hb-coretext.hh b/src/hb-coretext.hh index a58cf6c06..5f58d2abf 100644 --- a/src/hb-coretext.hh +++ b/src/hb-coretext.hh @@ -50,4 +50,4 @@ HB_INTERNAL CTFontRef create_ct_font (CGFontRef cg_font, CGFloat font_size); -#endif /* HB_OT_LAYOUT_HH */ +#endif /* HB_CORETEXT_HH */ diff --git a/src/hb-directwrite-shape.cc b/src/hb-directwrite-shape.cc new file mode 100644 index 000000000..c312ed492 --- /dev/null +++ b/src/hb-directwrite-shape.cc @@ -0,0 +1,622 @@ +/* + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Author(s): Behdad Esfahbod + */ + +#include "hb.hh" + +#ifdef HAVE_DIRECTWRITE + +#include "hb-shaper-impl.hh" + +#include "hb-directwrite.hh" + +#include "hb-ms-feature-ranges.hh" + + +/* +* shaper face data +*/ + +hb_directwrite_face_data_t * +_hb_directwrite_shaper_face_data_create (hb_face_t *face) +{ + hb_blob_t *blob = hb_face_reference_blob (face); + + hb_directwrite_face_data_t *data = (hb_directwrite_face_data_t *) dw_face_create (blob, face->index); + + hb_blob_destroy (blob); + + return data; +} + +void +_hb_directwrite_shaper_face_data_destroy (hb_directwrite_face_data_t *data) +{ + ((IDWriteFontFace *) data)->Release (); +} + + +/* + * shaper font data + */ + +struct hb_directwrite_font_data_t {}; + +hb_directwrite_font_data_t * +_hb_directwrite_shaper_font_data_create (hb_font_t *font) +{ + return (hb_directwrite_font_data_t *) HB_SHAPER_DATA_SUCCEEDED; +} + +void +_hb_directwrite_shaper_font_data_destroy (hb_directwrite_font_data_t *data) +{ + if (data != HB_SHAPER_DATA_SUCCEEDED) + ((IDWriteFont *) (const void *) data)->Release(); +} + + +// Most of TextAnalysis is originally written by Bas Schouten for Mozilla project +// but now is relicensed to MIT for HarfBuzz use +class TextAnalysis : public IDWriteTextAnalysisSource, public IDWriteTextAnalysisSink +{ +private: + hb_reference_count_t mRefCount; +public: + IFACEMETHOD (QueryInterface) (IID const& iid, OUT void** ppObject) + { return S_OK; } + IFACEMETHOD_ (ULONG, AddRef) () + { + return mRefCount.inc () + 1; + } + IFACEMETHOD_ (ULONG, Release) () + { + signed refCount = mRefCount.dec () - 1; + assert (refCount >= 0); + if (refCount) + return refCount; + delete this; + return 0; + } + + // A single contiguous run of characters containing the same analysis + // results. + struct Run + { + uint32_t mTextStart; // starting text position of this run + uint32_t mTextLength; // number of contiguous code units covered + uint32_t mGlyphStart; // starting glyph in the glyphs array + uint32_t mGlyphCount; // number of glyphs associated with this run + // text + DWRITE_SCRIPT_ANALYSIS mScript; + uint8_t mBidiLevel; + bool mIsSideways; + + bool ContainsTextPosition (uint32_t aTextPosition) const + { + return aTextPosition >= mTextStart && + aTextPosition < mTextStart + mTextLength; + } + + Run *nextRun; + }; + +public: + TextAnalysis (const wchar_t* text, uint32_t textLength, + const wchar_t* localeName, DWRITE_READING_DIRECTION readingDirection) + : mTextLength (textLength), mText (text), mLocaleName (localeName), + mReadingDirection (readingDirection), mCurrentRun (nullptr) + { + mRefCount.init (); + } + virtual ~TextAnalysis () + { + // delete runs, except mRunHead which is part of the TextAnalysis object + for (Run *run = mRunHead.nextRun; run;) + { + Run *origRun = run; + run = run->nextRun; + delete origRun; + } + } + + STDMETHODIMP + GenerateResults (IDWriteTextAnalyzer* textAnalyzer, Run **runHead) + { + // Analyzes the text using the script analyzer and returns + // the result as a series of runs. + + HRESULT hr = S_OK; + + // Initially start out with one result that covers the entire range. + // This result will be subdivided by the analysis processes. + mRunHead.mTextStart = 0; + mRunHead.mTextLength = mTextLength; + mRunHead.mBidiLevel = + (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); + mRunHead.nextRun = nullptr; + mCurrentRun = &mRunHead; + + // Call each of the analyzers in sequence, recording their results. + if (SUCCEEDED (hr = textAnalyzer->AnalyzeScript (this, 0, mTextLength, this))) + *runHead = &mRunHead; + + return hr; + } + + // IDWriteTextAnalysisSource implementation + + IFACEMETHODIMP + GetTextAtPosition (uint32_t textPosition, + OUT wchar_t const** textString, + OUT uint32_t* textLength) + { + if (textPosition >= mTextLength) + { + // No text at this position, valid query though. + *textString = nullptr; + *textLength = 0; + } + else + { + *textString = mText + textPosition; + *textLength = mTextLength - textPosition; + } + return S_OK; + } + + IFACEMETHODIMP + GetTextBeforePosition (uint32_t textPosition, + OUT wchar_t const** textString, + OUT uint32_t* textLength) + { + if (textPosition == 0 || textPosition > mTextLength) + { + // Either there is no text before here (== 0), or this + // is an invalid position. The query is considered valid though. + *textString = nullptr; + *textLength = 0; + } + else + { + *textString = mText; + *textLength = textPosition; + } + return S_OK; + } + + IFACEMETHODIMP_ (DWRITE_READING_DIRECTION) + GetParagraphReadingDirection () { return mReadingDirection; } + + IFACEMETHODIMP GetLocaleName (uint32_t textPosition, uint32_t* textLength, + wchar_t const** localeName) + { return S_OK; } + + IFACEMETHODIMP + GetNumberSubstitution (uint32_t textPosition, + OUT uint32_t* textLength, + OUT IDWriteNumberSubstitution** numberSubstitution) + { + // We do not support number substitution. + *numberSubstitution = nullptr; + *textLength = mTextLength - textPosition; + + return S_OK; + } + + // IDWriteTextAnalysisSink implementation + + IFACEMETHODIMP + SetScriptAnalysis (uint32_t textPosition, uint32_t textLength, + DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) + { + SetCurrentRun (textPosition); + SplitCurrentRun (textPosition); + while (textLength > 0) + { + Run *run = FetchNextRun (&textLength); + run->mScript = *scriptAnalysis; + } + + return S_OK; + } + + IFACEMETHODIMP + SetLineBreakpoints (uint32_t textPosition, + uint32_t textLength, + const DWRITE_LINE_BREAKPOINT* lineBreakpoints) + { return S_OK; } + + IFACEMETHODIMP SetBidiLevel (uint32_t textPosition, uint32_t textLength, + uint8_t explicitLevel, uint8_t resolvedLevel) + { return S_OK; } + + IFACEMETHODIMP + SetNumberSubstitution (uint32_t textPosition, uint32_t textLength, + IDWriteNumberSubstitution* numberSubstitution) + { return S_OK; } + +protected: + Run *FetchNextRun (IN OUT uint32_t* textLength) + { + // Used by the sink setters, this returns a reference to the next run. + // Position and length are adjusted to now point after the current run + // being returned. + + Run *origRun = mCurrentRun; + // Split the tail if needed (the length remaining is less than the + // current run's size). + if (*textLength < mCurrentRun->mTextLength) + SplitCurrentRun (mCurrentRun->mTextStart + *textLength); + else + // Just advance the current run. + mCurrentRun = mCurrentRun->nextRun; + *textLength -= origRun->mTextLength; + + // Return a reference to the run that was just current. + return origRun; + } + + void SetCurrentRun (uint32_t textPosition) + { + // Move the current run to the given position. + // Since the analyzers generally return results in a forward manner, + // this will usually just return early. If not, find the + // corresponding run for the text position. + + if (mCurrentRun && mCurrentRun->ContainsTextPosition (textPosition)) + return; + + for (Run *run = &mRunHead; run; run = run->nextRun) + if (run->ContainsTextPosition (textPosition)) + { + mCurrentRun = run; + return; + } + assert (0); // We should always be able to find the text position in one of our runs + } + + void SplitCurrentRun (uint32_t splitPosition) + { + if (!mCurrentRun) + { + assert (0); // SplitCurrentRun called without current run + // Shouldn't be calling this when no current run is set! + return; + } + // Split the current run. + if (splitPosition <= mCurrentRun->mTextStart) + { + // No need to split, already the start of a run + // or before it. Usually the first. + return; + } + Run *newRun = new Run; + + *newRun = *mCurrentRun; + + // Insert the new run in our linked list. + newRun->nextRun = mCurrentRun->nextRun; + mCurrentRun->nextRun = newRun; + + // Adjust runs' text positions and lengths. + uint32_t splitPoint = splitPosition - mCurrentRun->mTextStart; + newRun->mTextStart += splitPoint; + newRun->mTextLength -= splitPoint; + mCurrentRun->mTextLength = splitPoint; + mCurrentRun = newRun; + } + +protected: + // Input + // (weak references are fine here, since this class is a transient + // stack-based helper that doesn't need to copy data) + uint32_t mTextLength; + const wchar_t* mText; + const wchar_t* mLocaleName; + DWRITE_READING_DIRECTION mReadingDirection; + + // Current processing state. + Run *mCurrentRun; + + // Output is a list of runs starting here + Run mRunHead; +}; + +/* + * shaper + */ + +hb_bool_t +_hb_directwrite_shape (hb_shape_plan_t *shape_plan, + hb_font_t *font, + hb_buffer_t *buffer, + const hb_feature_t *features, + unsigned int num_features) +{ + hb_face_t *face = font->face; + IDWriteFontFace *fontFace = (IDWriteFontFace *) (const void *) face->data.directwrite; + auto *global = get_directwrite_global (); + if (unlikely (!global)) + return false; + IDWriteFactory *dwriteFactory = global->dwriteFactory; + + IDWriteTextAnalyzer* analyzer; + dwriteFactory->CreateTextAnalyzer (&analyzer); + + unsigned int scratch_size; + hb_buffer_t::scratch_buffer_t *scratch = buffer->get_scratch_buffer (&scratch_size); +#define ALLOCATE_ARRAY(Type, name, len) \ + Type *name = (Type *) scratch; \ + do { \ + unsigned int _consumed = DIV_CEIL ((len) * sizeof (Type), sizeof (*scratch)); \ + assert (_consumed <= scratch_size); \ + scratch += _consumed; \ + scratch_size -= _consumed; \ + } while (0) + +#define utf16_index() var1.u32 + + ALLOCATE_ARRAY (wchar_t, textString, buffer->len * 2); + + unsigned int chars_len = 0; + for (unsigned int i = 0; i < buffer->len; i++) + { + hb_codepoint_t c = buffer->info[i].codepoint; + buffer->info[i].utf16_index () = chars_len; + if (likely (c <= 0xFFFFu)) + textString[chars_len++] = c; + else if (unlikely (c > 0x10FFFFu)) + textString[chars_len++] = 0xFFFDu; + else + { + textString[chars_len++] = 0xD800u + ((c - 0x10000u) >> 10); + textString[chars_len++] = 0xDC00u + ((c - 0x10000u) & ((1u << 10) - 1)); + } + } + + ALLOCATE_ARRAY (WORD, log_clusters, chars_len); + /* Need log_clusters to assign features. */ + chars_len = 0; + for (unsigned int i = 0; i < buffer->len; i++) + { + hb_codepoint_t c = buffer->info[i].codepoint; + unsigned int cluster = buffer->info[i].cluster; + log_clusters[chars_len++] = cluster; + if (hb_in_range (c, 0x10000u, 0x10FFFFu)) + log_clusters[chars_len++] = cluster; /* Surrogates. */ + } + + DWRITE_READING_DIRECTION readingDirection; + readingDirection = buffer->props.direction ? + DWRITE_READING_DIRECTION_RIGHT_TO_LEFT : + DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; + + /* + * There's an internal 16-bit limit on some things inside the analyzer, + * but we never attempt to shape a word longer than 64K characters + * in a single gfxShapedWord, so we cannot exceed that limit. + */ + uint32_t textLength = chars_len; + + TextAnalysis analysis (textString, textLength, nullptr, readingDirection); + TextAnalysis::Run *runHead; + HRESULT hr; + hr = analysis.GenerateResults (analyzer, &runHead); + +#define FAIL(...) \ + HB_STMT_START { \ + DEBUG_MSG (DIRECTWRITE, nullptr, __VA_ARGS__); \ + return false; \ + } HB_STMT_END + + if (FAILED (hr)) + FAIL ("Analyzer failed to generate results."); + + uint32_t maxGlyphCount = 3 * textLength / 2 + 16; + uint32_t glyphCount; + bool isRightToLeft = HB_DIRECTION_IS_BACKWARD (buffer->props.direction); + + const wchar_t localeName[20] = {0}; + if (buffer->props.language) + mbstowcs ((wchar_t*) localeName, + hb_language_to_string (buffer->props.language), 20); + + /* + * Set up features. + */ + static_assert ((sizeof (DWRITE_TYPOGRAPHIC_FEATURES) == sizeof (hb_ms_features_t)), ""); + static_assert ((sizeof (DWRITE_FONT_FEATURE) == sizeof (hb_ms_feature_t)), ""); + hb_vector_t range_features; + hb_vector_t range_char_counts; + + // https://github.com/harfbuzz/harfbuzz/pull/5114 + // The data allocated by these two vectors are used by the above two, so they + // should remain alive as long as the above two are. + hb_vector_t feature_records; + hb_vector_t range_records; + if (num_features) + { + if (hb_ms_setup_features (features, num_features, feature_records, range_records)) + { + hb_ms_make_feature_ranges (feature_records, + range_records, + 0, + chars_len, + log_clusters, + range_features, + range_char_counts); + } + } + + uint16_t* clusterMap; + clusterMap = new uint16_t[textLength]; + DWRITE_SHAPING_TEXT_PROPERTIES* textProperties; + textProperties = new DWRITE_SHAPING_TEXT_PROPERTIES[textLength]; + +retry_getglyphs: + uint16_t* glyphIndices = new uint16_t[maxGlyphCount]; + DWRITE_SHAPING_GLYPH_PROPERTIES* glyphProperties; + glyphProperties = new DWRITE_SHAPING_GLYPH_PROPERTIES[maxGlyphCount]; + + hr = analyzer->GetGlyphs (textString, + chars_len, + fontFace, + false, + isRightToLeft, + &runHead->mScript, + localeName, + nullptr, + (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ, + range_char_counts.arrayZ, + range_features.length, + maxGlyphCount, + clusterMap, + textProperties, + glyphIndices, + glyphProperties, + &glyphCount); + + if (unlikely (hr == HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER))) + { + delete [] glyphIndices; + delete [] glyphProperties; + + maxGlyphCount *= 2; + + goto retry_getglyphs; + } + if (FAILED (hr)) + FAIL ("Analyzer failed to get glyphs."); + + float* glyphAdvances = new float[maxGlyphCount]; + DWRITE_GLYPH_OFFSET* glyphOffsets = new DWRITE_GLYPH_OFFSET[maxGlyphCount]; + + /* The -2 in the following is to compensate for possible + * alignment needed after the WORD array. sizeof (WORD) == 2. */ + unsigned int glyphs_size = (scratch_size * sizeof (int) - 2) + / (sizeof (WORD) + + sizeof (DWRITE_SHAPING_GLYPH_PROPERTIES) + + sizeof (int) + + sizeof (DWRITE_GLYPH_OFFSET) + + sizeof (uint32_t)); + ALLOCATE_ARRAY (uint32_t, vis_clusters, glyphs_size); + +#undef ALLOCATE_ARRAY + + unsigned fontEmSize = font->face->get_upem (); + + float x_mult = font->x_multf; + float y_mult = font->y_multf; + + hr = analyzer->GetGlyphPlacements (textString, + clusterMap, + textProperties, + chars_len, + glyphIndices, + glyphProperties, + glyphCount, + fontFace, + fontEmSize, + false, + isRightToLeft, + &runHead->mScript, + localeName, + (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ, + range_char_counts.arrayZ, + range_features.length, + glyphAdvances, + glyphOffsets); + + if (FAILED (hr)) + FAIL ("Analyzer failed to get glyph placements."); + + /* Ok, we've got everything we need, now compose output buffer, + * very, *very*, carefully! */ + + /* Calculate visual-clusters. That's what we ship. */ + for (unsigned int i = 0; i < glyphCount; i++) + vis_clusters[i] = (uint32_t) -1; + for (unsigned int i = 0; i < buffer->len; i++) + { + uint32_t *p = + &vis_clusters[log_clusters[buffer->info[i].utf16_index ()]]; + *p = hb_min (*p, buffer->info[i].cluster); + } + for (unsigned int i = 1; i < glyphCount; i++) + if (vis_clusters[i] == (uint32_t) -1) + vis_clusters[i] = vis_clusters[i - 1]; + +#undef utf16_index + + if (unlikely (!buffer->ensure (glyphCount))) + FAIL ("Buffer in error"); + +#undef FAIL + + /* Set glyph infos */ + buffer->len = 0; + for (unsigned int i = 0; i < glyphCount; i++) + { + hb_glyph_info_t *info = &buffer->info[buffer->len++]; + + info->codepoint = glyphIndices[i]; + info->cluster = vis_clusters[i]; + + /* The rest is crap. Let's store position info there for now. */ + info->mask = glyphAdvances[i]; + info->var1.i32 = glyphOffsets[i].advanceOffset; + info->var2.i32 = glyphOffsets[i].ascenderOffset; + } + + /* Set glyph positions */ + buffer->clear_positions (); + for (unsigned int i = 0; i < glyphCount; i++) + { + hb_glyph_info_t *info = &buffer->info[i]; + hb_glyph_position_t *pos = &buffer->pos[i]; + + /* TODO vertical */ + pos->x_advance = round (x_mult * (int32_t) info->mask); + pos->x_offset = round (x_mult * (isRightToLeft ? -info->var1.i32 : info->var1.i32)); + pos->y_offset = round (y_mult * info->var2.i32); + } + + if (isRightToLeft) hb_buffer_reverse (buffer); + + buffer->clear_glyph_flags (); + buffer->unsafe_to_break (); + + delete [] clusterMap; + delete [] glyphIndices; + delete [] textProperties; + delete [] glyphProperties; + delete [] glyphAdvances; + delete [] glyphOffsets; + + /* Wow, done! */ + return true; +} + + +#endif diff --git a/src/hb-directwrite.cc b/src/hb-directwrite.cc index 2483f9ddd..d04e0c2e5 100644 --- a/src/hb-directwrite.cc +++ b/src/hb-directwrite.cc @@ -26,14 +26,9 @@ #ifdef HAVE_DIRECTWRITE -#include "hb-shaper-impl.hh" +#include "hb-directwrite.hh" -#include "hb-directwrite.h" - -#include "hb-ms-feature-ranges.hh" - -#include "hb-mutex.hh" -#include "hb-map.hh" +#include "hb-font.hh" /** @@ -45,205 +40,6 @@ * Functions for using HarfBuzz with DirectWrite fonts. **/ -/* Declare object creator for dynamic support of DWRITE */ -typedef HRESULT (WINAPI *t_DWriteCreateFactory)( - DWRITE_FACTORY_TYPE factoryType, - REFIID iid, - IUnknown **factory -); - - -/* - * DirectWrite font stream helpers - */ - -// Have a look at to NativeFontResourceDWrite.cpp in Mozilla - -class DWriteFontFileLoader : public IDWriteFontFileLoader -{ -private: - hb_reference_count_t mRefCount; - hb_mutex_t mutex; - hb_hashmap_t mFontStreams; - uint64_t mNextFontFileKey = 0; -public: - DWriteFontFileLoader () - { - mRefCount.init (); - } - - uint64_t RegisterFontFileStream (IDWriteFontFileStream *fontFileStream) - { - fontFileStream->AddRef (); - auto lock = hb_lock_t (mutex); - mFontStreams.set (mNextFontFileKey, fontFileStream); - return mNextFontFileKey++; - } - void UnregisterFontFileStream (uint64_t fontFileKey) - { - auto lock = hb_lock_t (mutex); - IDWriteFontFileStream *stream = mFontStreams.get (fontFileKey); - if (stream) - { - mFontStreams.del (fontFileKey); - stream->Release (); - } - } - - // IUnknown interface - IFACEMETHOD (QueryInterface) (IID const& iid, OUT void** ppObject) - { return S_OK; } - IFACEMETHOD_ (ULONG, AddRef) () - { - return mRefCount.inc () + 1; - } - IFACEMETHOD_ (ULONG, Release) () - { - signed refCount = mRefCount.dec () - 1; - assert (refCount >= 0); - if (refCount) - return refCount; - delete this; - return 0; - } - - // IDWriteFontFileLoader methods - virtual HRESULT STDMETHODCALLTYPE - CreateStreamFromKey (void const* fontFileReferenceKey, - uint32_t fontFileReferenceKeySize, - OUT IDWriteFontFileStream** fontFileStream) - { - if (fontFileReferenceKeySize != sizeof (uint64_t)) - return E_INVALIDARG; - uint64_t fontFileKey = * (uint64_t *) fontFileReferenceKey; - IDWriteFontFileStream *stream = mFontStreams.get (fontFileKey); - if (!stream) - return E_FAIL; - stream->AddRef (); - *fontFileStream = stream; - return S_OK; - } - - virtual ~DWriteFontFileLoader() - { - for (auto v : mFontStreams.values ()) - v->Release (); - } -}; - -class DWriteFontFileStream : public IDWriteFontFileStream -{ -private: - hb_reference_count_t mRefCount; - hb_blob_t *mBlob; - uint8_t *mData; - unsigned mSize; - DWriteFontFileLoader *mLoader; -public: - uint64_t fontFileKey; -public: - DWriteFontFileStream (hb_blob_t *blob); - - // IUnknown interface - IFACEMETHOD (QueryInterface) (IID const& iid, OUT void** ppObject) - { return S_OK; } - IFACEMETHOD_ (ULONG, AddRef) () - { - return mRefCount.inc () + 1; - } - IFACEMETHOD_ (ULONG, Release) () - { - signed refCount = mRefCount.dec () - 1; - assert (refCount >= 0); - if (refCount) - return refCount; - delete this; - return 0; - } - - // IDWriteFontFileStream methods - virtual HRESULT STDMETHODCALLTYPE - ReadFileFragment (void const** fragmentStart, - UINT64 fileOffset, - UINT64 fragmentSize, - OUT void** fragmentContext) - { - // We are required to do bounds checking. - if (fileOffset + fragmentSize > mSize) return E_FAIL; - - // truncate the 64 bit fileOffset to size_t sized index into mData - size_t index = static_cast (fileOffset); - - // We should be alive for the duration of this. - *fragmentStart = &mData[index]; - *fragmentContext = nullptr; - return S_OK; - } - - virtual void STDMETHODCALLTYPE - ReleaseFileFragment (void* fragmentContext) {} - - virtual HRESULT STDMETHODCALLTYPE - GetFileSize (OUT UINT64* fileSize) - { - *fileSize = mSize; - return S_OK; - } - - virtual HRESULT STDMETHODCALLTYPE - GetLastWriteTime (OUT UINT64* lastWriteTime) { return E_NOTIMPL; } - - virtual ~DWriteFontFileStream(); -}; - -struct hb_directwrite_global_t -{ - hb_directwrite_global_t () - { - dwrite_dll = LoadLibraryW (L"DWrite.dll"); - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - - t_DWriteCreateFactory p_DWriteCreateFactory = (t_DWriteCreateFactory) - GetProcAddress (dwrite_dll, "DWriteCreateFactory"); - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - - if (unlikely (!p_DWriteCreateFactory)) - return; - - HRESULT hr = p_DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory), - (IUnknown**) &dwriteFactory); - - if (unlikely (hr != S_OK)) - return; - - fontFileLoader = new DWriteFontFileLoader (); - dwriteFactory->RegisterFontFileLoader (fontFileLoader); - - success = true; - } - ~hb_directwrite_global_t () - { - if (fontFileLoader) - fontFileLoader->Release (); - if (dwriteFactory) - dwriteFactory->Release (); - if (dwrite_dll) - FreeLibrary (dwrite_dll); - } - - bool success = false; - HMODULE dwrite_dll; - IDWriteFactory *dwriteFactory; - DWriteFontFileLoader *fontFileLoader; -}; - static inline void free_static_directwrite_global (); static struct hb_directwrite_global_lazy_loader_t : hb_lazy_loader_tindex); - - hb_blob_destroy (blob); - - return data; -} - -void -_hb_directwrite_shaper_face_data_destroy (hb_directwrite_face_data_t *data) -{ - ((IDWriteFontFace *) data)->Release (); -} - - -/* - * shaper font data - */ - -struct hb_directwrite_font_data_t {}; - -hb_directwrite_font_data_t * -_hb_directwrite_shaper_font_data_create (hb_font_t *font) -{ - return (hb_directwrite_font_data_t *) HB_SHAPER_DATA_SUCCEEDED; -} - -void -_hb_directwrite_shaper_font_data_destroy (hb_directwrite_font_data_t *data) -{ - if (data != HB_SHAPER_DATA_SUCCEEDED) - ((IDWriteFont *) (const void *) data)->Release(); -} - - -// Most of TextAnalysis is originally written by Bas Schouten for Mozilla project -// but now is relicensed to MIT for HarfBuzz use -class TextAnalysis : public IDWriteTextAnalysisSource, public IDWriteTextAnalysisSink -{ -private: - hb_reference_count_t mRefCount; -public: - IFACEMETHOD (QueryInterface) (IID const& iid, OUT void** ppObject) - { return S_OK; } - IFACEMETHOD_ (ULONG, AddRef) () - { - return mRefCount.inc () + 1; - } - IFACEMETHOD_ (ULONG, Release) () - { - signed refCount = mRefCount.dec () - 1; - assert (refCount >= 0); - if (refCount) - return refCount; - delete this; - return 0; - } - - // A single contiguous run of characters containing the same analysis - // results. - struct Run - { - uint32_t mTextStart; // starting text position of this run - uint32_t mTextLength; // number of contiguous code units covered - uint32_t mGlyphStart; // starting glyph in the glyphs array - uint32_t mGlyphCount; // number of glyphs associated with this run - // text - DWRITE_SCRIPT_ANALYSIS mScript; - uint8_t mBidiLevel; - bool mIsSideways; - - bool ContainsTextPosition (uint32_t aTextPosition) const - { - return aTextPosition >= mTextStart && - aTextPosition < mTextStart + mTextLength; - } - - Run *nextRun; - }; - -public: - TextAnalysis (const wchar_t* text, uint32_t textLength, - const wchar_t* localeName, DWRITE_READING_DIRECTION readingDirection) - : mTextLength (textLength), mText (text), mLocaleName (localeName), - mReadingDirection (readingDirection), mCurrentRun (nullptr) - { - mRefCount.init (); - } - virtual ~TextAnalysis () - { - // delete runs, except mRunHead which is part of the TextAnalysis object - for (Run *run = mRunHead.nextRun; run;) - { - Run *origRun = run; - run = run->nextRun; - delete origRun; - } - } - - STDMETHODIMP - GenerateResults (IDWriteTextAnalyzer* textAnalyzer, Run **runHead) - { - // Analyzes the text using the script analyzer and returns - // the result as a series of runs. - - HRESULT hr = S_OK; - - // Initially start out with one result that covers the entire range. - // This result will be subdivided by the analysis processes. - mRunHead.mTextStart = 0; - mRunHead.mTextLength = mTextLength; - mRunHead.mBidiLevel = - (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); - mRunHead.nextRun = nullptr; - mCurrentRun = &mRunHead; - - // Call each of the analyzers in sequence, recording their results. - if (SUCCEEDED (hr = textAnalyzer->AnalyzeScript (this, 0, mTextLength, this))) - *runHead = &mRunHead; - - return hr; - } - - // IDWriteTextAnalysisSource implementation - - IFACEMETHODIMP - GetTextAtPosition (uint32_t textPosition, - OUT wchar_t const** textString, - OUT uint32_t* textLength) - { - if (textPosition >= mTextLength) - { - // No text at this position, valid query though. - *textString = nullptr; - *textLength = 0; - } - else - { - *textString = mText + textPosition; - *textLength = mTextLength - textPosition; - } - return S_OK; - } - - IFACEMETHODIMP - GetTextBeforePosition (uint32_t textPosition, - OUT wchar_t const** textString, - OUT uint32_t* textLength) - { - if (textPosition == 0 || textPosition > mTextLength) - { - // Either there is no text before here (== 0), or this - // is an invalid position. The query is considered valid though. - *textString = nullptr; - *textLength = 0; - } - else - { - *textString = mText; - *textLength = textPosition; - } - return S_OK; - } - - IFACEMETHODIMP_ (DWRITE_READING_DIRECTION) - GetParagraphReadingDirection () { return mReadingDirection; } - - IFACEMETHODIMP GetLocaleName (uint32_t textPosition, uint32_t* textLength, - wchar_t const** localeName) - { return S_OK; } - - IFACEMETHODIMP - GetNumberSubstitution (uint32_t textPosition, - OUT uint32_t* textLength, - OUT IDWriteNumberSubstitution** numberSubstitution) - { - // We do not support number substitution. - *numberSubstitution = nullptr; - *textLength = mTextLength - textPosition; - - return S_OK; - } - - // IDWriteTextAnalysisSink implementation - - IFACEMETHODIMP - SetScriptAnalysis (uint32_t textPosition, uint32_t textLength, - DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis) - { - SetCurrentRun (textPosition); - SplitCurrentRun (textPosition); - while (textLength > 0) - { - Run *run = FetchNextRun (&textLength); - run->mScript = *scriptAnalysis; - } - - return S_OK; - } - - IFACEMETHODIMP - SetLineBreakpoints (uint32_t textPosition, - uint32_t textLength, - const DWRITE_LINE_BREAKPOINT* lineBreakpoints) - { return S_OK; } - - IFACEMETHODIMP SetBidiLevel (uint32_t textPosition, uint32_t textLength, - uint8_t explicitLevel, uint8_t resolvedLevel) - { return S_OK; } - - IFACEMETHODIMP - SetNumberSubstitution (uint32_t textPosition, uint32_t textLength, - IDWriteNumberSubstitution* numberSubstitution) - { return S_OK; } - -protected: - Run *FetchNextRun (IN OUT uint32_t* textLength) - { - // Used by the sink setters, this returns a reference to the next run. - // Position and length are adjusted to now point after the current run - // being returned. - - Run *origRun = mCurrentRun; - // Split the tail if needed (the length remaining is less than the - // current run's size). - if (*textLength < mCurrentRun->mTextLength) - SplitCurrentRun (mCurrentRun->mTextStart + *textLength); - else - // Just advance the current run. - mCurrentRun = mCurrentRun->nextRun; - *textLength -= origRun->mTextLength; - - // Return a reference to the run that was just current. - return origRun; - } - - void SetCurrentRun (uint32_t textPosition) - { - // Move the current run to the given position. - // Since the analyzers generally return results in a forward manner, - // this will usually just return early. If not, find the - // corresponding run for the text position. - - if (mCurrentRun && mCurrentRun->ContainsTextPosition (textPosition)) - return; - - for (Run *run = &mRunHead; run; run = run->nextRun) - if (run->ContainsTextPosition (textPosition)) - { - mCurrentRun = run; - return; - } - assert (0); // We should always be able to find the text position in one of our runs - } - - void SplitCurrentRun (uint32_t splitPosition) - { - if (!mCurrentRun) - { - assert (0); // SplitCurrentRun called without current run - // Shouldn't be calling this when no current run is set! - return; - } - // Split the current run. - if (splitPosition <= mCurrentRun->mTextStart) - { - // No need to split, already the start of a run - // or before it. Usually the first. - return; - } - Run *newRun = new Run; - - *newRun = *mCurrentRun; - - // Insert the new run in our linked list. - newRun->nextRun = mCurrentRun->nextRun; - mCurrentRun->nextRun = newRun; - - // Adjust runs' text positions and lengths. - uint32_t splitPoint = splitPosition - mCurrentRun->mTextStart; - newRun->mTextStart += splitPoint; - newRun->mTextLength -= splitPoint; - mCurrentRun->mTextLength = splitPoint; - mCurrentRun = newRun; - } - -protected: - // Input - // (weak references are fine here, since this class is a transient - // stack-based helper that doesn't need to copy data) - uint32_t mTextLength; - const wchar_t* mText; - const wchar_t* mLocaleName; - DWRITE_READING_DIRECTION mReadingDirection; - - // Current processing state. - Run *mCurrentRun; - - // Output is a list of runs starting here - Run mRunHead; -}; - -/* - * shaper - */ - -hb_bool_t -_hb_directwrite_shape (hb_shape_plan_t *shape_plan, - hb_font_t *font, - hb_buffer_t *buffer, - const hb_feature_t *features, - unsigned int num_features) -{ - hb_face_t *face = font->face; - IDWriteFontFace *fontFace = (IDWriteFontFace *) (const void *) face->data.directwrite; - auto *global = get_directwrite_global (); - if (unlikely (!global)) - return false; - IDWriteFactory *dwriteFactory = global->dwriteFactory; - - IDWriteTextAnalyzer* analyzer; - dwriteFactory->CreateTextAnalyzer (&analyzer); - - unsigned int scratch_size; - hb_buffer_t::scratch_buffer_t *scratch = buffer->get_scratch_buffer (&scratch_size); -#define ALLOCATE_ARRAY(Type, name, len) \ - Type *name = (Type *) scratch; \ - do { \ - unsigned int _consumed = DIV_CEIL ((len) * sizeof (Type), sizeof (*scratch)); \ - assert (_consumed <= scratch_size); \ - scratch += _consumed; \ - scratch_size -= _consumed; \ - } while (0) - -#define utf16_index() var1.u32 - - ALLOCATE_ARRAY (wchar_t, textString, buffer->len * 2); - - unsigned int chars_len = 0; - for (unsigned int i = 0; i < buffer->len; i++) - { - hb_codepoint_t c = buffer->info[i].codepoint; - buffer->info[i].utf16_index () = chars_len; - if (likely (c <= 0xFFFFu)) - textString[chars_len++] = c; - else if (unlikely (c > 0x10FFFFu)) - textString[chars_len++] = 0xFFFDu; - else - { - textString[chars_len++] = 0xD800u + ((c - 0x10000u) >> 10); - textString[chars_len++] = 0xDC00u + ((c - 0x10000u) & ((1u << 10) - 1)); - } - } - - ALLOCATE_ARRAY (WORD, log_clusters, chars_len); - /* Need log_clusters to assign features. */ - chars_len = 0; - for (unsigned int i = 0; i < buffer->len; i++) - { - hb_codepoint_t c = buffer->info[i].codepoint; - unsigned int cluster = buffer->info[i].cluster; - log_clusters[chars_len++] = cluster; - if (hb_in_range (c, 0x10000u, 0x10FFFFu)) - log_clusters[chars_len++] = cluster; /* Surrogates. */ - } - - DWRITE_READING_DIRECTION readingDirection; - readingDirection = buffer->props.direction ? - DWRITE_READING_DIRECTION_RIGHT_TO_LEFT : - DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; - - /* - * There's an internal 16-bit limit on some things inside the analyzer, - * but we never attempt to shape a word longer than 64K characters - * in a single gfxShapedWord, so we cannot exceed that limit. - */ - uint32_t textLength = chars_len; - - TextAnalysis analysis (textString, textLength, nullptr, readingDirection); - TextAnalysis::Run *runHead; - HRESULT hr; - hr = analysis.GenerateResults (analyzer, &runHead); - -#define FAIL(...) \ - HB_STMT_START { \ - DEBUG_MSG (DIRECTWRITE, nullptr, __VA_ARGS__); \ - return false; \ - } HB_STMT_END - - if (FAILED (hr)) - FAIL ("Analyzer failed to generate results."); - - uint32_t maxGlyphCount = 3 * textLength / 2 + 16; - uint32_t glyphCount; - bool isRightToLeft = HB_DIRECTION_IS_BACKWARD (buffer->props.direction); - - const wchar_t localeName[20] = {0}; - if (buffer->props.language) - mbstowcs ((wchar_t*) localeName, - hb_language_to_string (buffer->props.language), 20); - - /* - * Set up features. - */ - static_assert ((sizeof (DWRITE_TYPOGRAPHIC_FEATURES) == sizeof (hb_ms_features_t)), ""); - static_assert ((sizeof (DWRITE_FONT_FEATURE) == sizeof (hb_ms_feature_t)), ""); - hb_vector_t range_features; - hb_vector_t range_char_counts; - - // https://github.com/harfbuzz/harfbuzz/pull/5114 - // The data allocated by these two vectors are used by the above two, so they - // should remain alive as long as the above two are. - hb_vector_t feature_records; - hb_vector_t range_records; - if (num_features) - { - if (hb_ms_setup_features (features, num_features, feature_records, range_records)) - { - hb_ms_make_feature_ranges (feature_records, - range_records, - 0, - chars_len, - log_clusters, - range_features, - range_char_counts); - } - } - - uint16_t* clusterMap; - clusterMap = new uint16_t[textLength]; - DWRITE_SHAPING_TEXT_PROPERTIES* textProperties; - textProperties = new DWRITE_SHAPING_TEXT_PROPERTIES[textLength]; - -retry_getglyphs: - uint16_t* glyphIndices = new uint16_t[maxGlyphCount]; - DWRITE_SHAPING_GLYPH_PROPERTIES* glyphProperties; - glyphProperties = new DWRITE_SHAPING_GLYPH_PROPERTIES[maxGlyphCount]; - - hr = analyzer->GetGlyphs (textString, - chars_len, - fontFace, - false, - isRightToLeft, - &runHead->mScript, - localeName, - nullptr, - (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ, - range_char_counts.arrayZ, - range_features.length, - maxGlyphCount, - clusterMap, - textProperties, - glyphIndices, - glyphProperties, - &glyphCount); - - if (unlikely (hr == HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER))) - { - delete [] glyphIndices; - delete [] glyphProperties; - - maxGlyphCount *= 2; - - goto retry_getglyphs; - } - if (FAILED (hr)) - FAIL ("Analyzer failed to get glyphs."); - - float* glyphAdvances = new float[maxGlyphCount]; - DWRITE_GLYPH_OFFSET* glyphOffsets = new DWRITE_GLYPH_OFFSET[maxGlyphCount]; - - /* The -2 in the following is to compensate for possible - * alignment needed after the WORD array. sizeof (WORD) == 2. */ - unsigned int glyphs_size = (scratch_size * sizeof (int) - 2) - / (sizeof (WORD) + - sizeof (DWRITE_SHAPING_GLYPH_PROPERTIES) + - sizeof (int) + - sizeof (DWRITE_GLYPH_OFFSET) + - sizeof (uint32_t)); - ALLOCATE_ARRAY (uint32_t, vis_clusters, glyphs_size); - -#undef ALLOCATE_ARRAY - - unsigned fontEmSize = font->face->get_upem (); - - float x_mult = font->x_multf; - float y_mult = font->y_multf; - - hr = analyzer->GetGlyphPlacements (textString, - clusterMap, - textProperties, - chars_len, - glyphIndices, - glyphProperties, - glyphCount, - fontFace, - fontEmSize, - false, - isRightToLeft, - &runHead->mScript, - localeName, - (const DWRITE_TYPOGRAPHIC_FEATURES**) range_features.arrayZ, - range_char_counts.arrayZ, - range_features.length, - glyphAdvances, - glyphOffsets); - - if (FAILED (hr)) - FAIL ("Analyzer failed to get glyph placements."); - - /* Ok, we've got everything we need, now compose output buffer, - * very, *very*, carefully! */ - - /* Calculate visual-clusters. That's what we ship. */ - for (unsigned int i = 0; i < glyphCount; i++) - vis_clusters[i] = (uint32_t) -1; - for (unsigned int i = 0; i < buffer->len; i++) - { - uint32_t *p = - &vis_clusters[log_clusters[buffer->info[i].utf16_index ()]]; - *p = hb_min (*p, buffer->info[i].cluster); - } - for (unsigned int i = 1; i < glyphCount; i++) - if (vis_clusters[i] == (uint32_t) -1) - vis_clusters[i] = vis_clusters[i - 1]; - -#undef utf16_index - - if (unlikely (!buffer->ensure (glyphCount))) - FAIL ("Buffer in error"); - -#undef FAIL - - /* Set glyph infos */ - buffer->len = 0; - for (unsigned int i = 0; i < glyphCount; i++) - { - hb_glyph_info_t *info = &buffer->info[buffer->len++]; - - info->codepoint = glyphIndices[i]; - info->cluster = vis_clusters[i]; - - /* The rest is crap. Let's store position info there for now. */ - info->mask = glyphAdvances[i]; - info->var1.i32 = glyphOffsets[i].advanceOffset; - info->var2.i32 = glyphOffsets[i].ascenderOffset; - } - - /* Set glyph positions */ - buffer->clear_positions (); - for (unsigned int i = 0; i < glyphCount; i++) - { - hb_glyph_info_t *info = &buffer->info[i]; - hb_glyph_position_t *pos = &buffer->pos[i]; - - /* TODO vertical */ - pos->x_advance = round (x_mult * (int32_t) info->mask); - pos->x_offset = round (x_mult * (isRightToLeft ? -info->var1.i32 : info->var1.i32)); - pos->y_offset = round (y_mult * info->var2.i32); - } - - if (isRightToLeft) hb_buffer_reverse (buffer); - - buffer->clear_glyph_flags (); - buffer->unsafe_to_break (); - - delete [] clusterMap; - delete [] glyphIndices; - delete [] textProperties; - delete [] glyphProperties; - delete [] glyphAdvances; - delete [] glyphOffsets; - - /* Wow, done! */ - return true; -} - struct _hb_directwrite_font_table_context { IDWriteFontFace *face; void *table_context; diff --git a/src/hb-directwrite.hh b/src/hb-directwrite.hh new file mode 100644 index 000000000..f51ab059a --- /dev/null +++ b/src/hb-directwrite.hh @@ -0,0 +1,243 @@ +/* + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Author(s): Behdad Esfahbod + */ + + +#ifndef HB_DIRECTWRITE_HH +#define HB_DIRECTWRITE_HH + +#include "hb.hh" + +#include "hb-directwrite.h" + +#include "hb-mutex.hh" +#include "hb-map.hh" + +/* + * DirectWrite font stream helpers + */ + +// Have a look at to NativeFontResourceDWrite.cpp in Mozilla + + +/* Declare object creator for dynamic support of DWRITE */ +typedef HRESULT (WINAPI *t_DWriteCreateFactory)( + DWRITE_FACTORY_TYPE factoryType, + REFIID iid, + IUnknown **factory +); + +class DWriteFontFileLoader : public IDWriteFontFileLoader +{ +private: + hb_reference_count_t mRefCount; + hb_mutex_t mutex; + hb_hashmap_t mFontStreams; + uint64_t mNextFontFileKey = 0; +public: + DWriteFontFileLoader () + { + mRefCount.init (); + } + + uint64_t RegisterFontFileStream (IDWriteFontFileStream *fontFileStream) + { + fontFileStream->AddRef (); + auto lock = hb_lock_t (mutex); + mFontStreams.set (mNextFontFileKey, fontFileStream); + return mNextFontFileKey++; + } + void UnregisterFontFileStream (uint64_t fontFileKey) + { + auto lock = hb_lock_t (mutex); + IDWriteFontFileStream *stream = mFontStreams.get (fontFileKey); + if (stream) + { + mFontStreams.del (fontFileKey); + stream->Release (); + } + } + + // IUnknown interface + IFACEMETHOD (QueryInterface) (IID const& iid, OUT void** ppObject) + { return S_OK; } + IFACEMETHOD_ (ULONG, AddRef) () + { + return mRefCount.inc () + 1; + } + IFACEMETHOD_ (ULONG, Release) () + { + signed refCount = mRefCount.dec () - 1; + assert (refCount >= 0); + if (refCount) + return refCount; + delete this; + return 0; + } + + // IDWriteFontFileLoader methods + virtual HRESULT STDMETHODCALLTYPE + CreateStreamFromKey (void const* fontFileReferenceKey, + uint32_t fontFileReferenceKeySize, + OUT IDWriteFontFileStream** fontFileStream) + { + if (fontFileReferenceKeySize != sizeof (uint64_t)) + return E_INVALIDARG; + uint64_t fontFileKey = * (uint64_t *) fontFileReferenceKey; + IDWriteFontFileStream *stream = mFontStreams.get (fontFileKey); + if (!stream) + return E_FAIL; + stream->AddRef (); + *fontFileStream = stream; + return S_OK; + } + + virtual ~DWriteFontFileLoader() + { + for (auto v : mFontStreams.values ()) + v->Release (); + } +}; + +class DWriteFontFileStream : public IDWriteFontFileStream +{ +private: + hb_reference_count_t mRefCount; + hb_blob_t *mBlob; + uint8_t *mData; + unsigned mSize; + DWriteFontFileLoader *mLoader; +public: + uint64_t fontFileKey; +public: + DWriteFontFileStream (hb_blob_t *blob); + + // IUnknown interface + IFACEMETHOD (QueryInterface) (IID const& iid, OUT void** ppObject) + { return S_OK; } + IFACEMETHOD_ (ULONG, AddRef) () + { + return mRefCount.inc () + 1; + } + IFACEMETHOD_ (ULONG, Release) () + { + signed refCount = mRefCount.dec () - 1; + assert (refCount >= 0); + if (refCount) + return refCount; + delete this; + return 0; + } + + // IDWriteFontFileStream methods + virtual HRESULT STDMETHODCALLTYPE + ReadFileFragment (void const** fragmentStart, + UINT64 fileOffset, + UINT64 fragmentSize, + OUT void** fragmentContext) + { + // We are required to do bounds checking. + if (fileOffset + fragmentSize > mSize) return E_FAIL; + + // truncate the 64 bit fileOffset to size_t sized index into mData + size_t index = static_cast (fileOffset); + + // We should be alive for the duration of this. + *fragmentStart = &mData[index]; + *fragmentContext = nullptr; + return S_OK; + } + + virtual void STDMETHODCALLTYPE + ReleaseFileFragment (void* fragmentContext) {} + + virtual HRESULT STDMETHODCALLTYPE + GetFileSize (OUT UINT64* fileSize) + { + *fileSize = mSize; + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE + GetLastWriteTime (OUT UINT64* lastWriteTime) { return E_NOTIMPL; } + + virtual ~DWriteFontFileStream(); +}; + +struct hb_directwrite_global_t +{ + hb_directwrite_global_t () + { + dwrite_dll = LoadLibraryW (L"DWrite.dll"); + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + + t_DWriteCreateFactory p_DWriteCreateFactory = (t_DWriteCreateFactory) + GetProcAddress (dwrite_dll, "DWriteCreateFactory"); + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + + if (unlikely (!p_DWriteCreateFactory)) + return; + + HRESULT hr = p_DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory), + (IUnknown**) &dwriteFactory); + + if (unlikely (hr != S_OK)) + return; + + fontFileLoader = new DWriteFontFileLoader (); + dwriteFactory->RegisterFontFileLoader (fontFileLoader); + + success = true; + } + ~hb_directwrite_global_t () + { + if (fontFileLoader) + fontFileLoader->Release (); + if (dwriteFactory) + dwriteFactory->Release (); + if (dwrite_dll) + FreeLibrary (dwrite_dll); + } + + bool success = false; + HMODULE dwrite_dll; + IDWriteFactory *dwriteFactory; + DWriteFontFileLoader *fontFileLoader; +}; + + +HB_INTERNAL hb_directwrite_global_t * +get_directwrite_global (); + +HB_INTERNAL IDWriteFontFace * +dw_face_create (hb_blob_t *blob, unsigned index); + + +#endif /* HB_DIRECTWRITE_HH */ diff --git a/src/meson.build b/src/meson.build index b53a98079..5047e11a9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -356,7 +356,7 @@ hb_wasm_headers = files() hb_coretext_sources = files('hb-coretext.cc', 'hb-coretext-shape.cc', 'hb-coretext-font.cc') hb_coretext_headers = files('hb-coretext.h') -hb_directwrite_sources = files('hb-directwrite.cc') +hb_directwrite_sources = files('hb-directwrite.cc', 'hb-directwrite-shape.cc') hb_directwrite_headers = files('hb-directwrite.h') hb_gdi_sources = files('hb-gdi.cc')