spelling/ru

From Free Pascal wiki

English (en) русский (ru)



Использование hunspell с Lazarus

Эта страница актуальна по состоянию на август 2018 года, но все меняется .....

Эта страница посвящена использованию библиотеки hunspell с Lazarus. Он описывает модель, которая работает, вроде как. Вам почти наверняка понадобится внести некоторые изменения для ваших конкретных целей, но, надеюсь, эта страница послужит вам хорошим началом.

Во-первых, на форуме есть несколько ссылок на некоторый код, который будет работать с библиотекой hunspell. Модуль hunspell.pas в значительной степени основан на этих блоках кода. У большинства нет информации о лицензии, и делается предположение, что она является «общеизвестной» и, следовательно, свободна от каких-либо ограничений. Я добавил немного, что решает проблему поиска файлов библиотеки и словаря. И установил разумный интерфейс.

Кроме того, пользователь rvk с форума создал Windows 64-битную DLL, так как для пользователей Windows не было никакой альтернативы.


О библиотеке Hunspell

Hunspell - это активный проект с открытым исходным кодом, распространяемый по открытой лицензии Mozilla. Библиотека hunspell используется в таких продуктах, как Libra Office, Open Office и Firefox. Его можно заставить работать на Windows, Linux и Mac (и, возможно, на кучах других платформ). См. [ссылки на] платформоспецифичные страницы ниже. Словари Hunspell легко доступны и, возможно, уже установлены на многих машинах. Даже если вы не можете получить доступ к библиотеке другого приложения, вы можете использовать его словарь.

Словари Hunspell поставляются в виде пары файлов *.dic и *.aff. Например, австралийский словарь состоит из en_AU.dic и en_AU.aff. [Префикс] 'en' обозначает его английский, а [суффикс] 'AU' говорит о его [специфичности] специально для Австралии. Как говорящий по-английски, я отмечаю, что словари en_US, кажется, всегда установлены, и я добавляю австралийские. Я не знаю, насколько распространен этот шаблон в не-англоязычных системах.

Платформозависимость

Linux

Во многих дистрибутивах Linux по умолчанию установлен Hunspell вместе с соответствующими языковыми словарями. Если нет, то, вероятно, это просто случай использования менеджера пакетов дистрибутива. Если ничего не помогает, возьмите исходник с сайта hunspell github и создайте его самостоятельно. Пользователи Linux любят это.

Чтобы проверить, установлена ли у вас библиотека hunspell, попробуйте эту команду - ldconfig -p | grep hunspell. Точно так же вы можете найти некоторые словари с помощью ls -l /usr/share/hunspell. Если это не сработает, попробуйте find /usr -name *.aff, это займет немного больше времени.

Windows

Установка библиотеки hunspell на Windows является более серьезной проблемой. По-видимому, нет предварительно скомпилированного 'комплекта', и большинство приложений Windows, которые используют Hunspell, похоже, статически связывают его, поэтому не осталось никаких hunspell.dll для использования. Но просто, чтобы быть уверенным, попробуйте поискать *hunspell*.dll. На сайте Hunspell github приведен рецепт его создания, но он включает установку MSYS2 и довольно сложен. Получающаяся DLL также нуждается вдобавок в паре gcc DLL.

К счастью, пользователь rvk на форуме Lazarus создал нам хорошую статически (т.е. автономно) связанную DLL-библиотеку с использованием Microsoft Visual Studio Community 2015. Таким образом, вы можете использовать и распространять эту DLL-библиотеку вместе с вашей программой, подпадающей под действие публичной лицензии Mozilla.

Вы найдете эту DLL в комплекте с 64-битной (предварительной) версией tomboy-ng, просто скачайте zip-файл, распакуйте и выбросьте (как печально) бинарный файл tomboy-ng. Смотрите https://github.com/tomboy-notes/tomboy-ng/releases



Прим.перев.: Вы также можете посмотреть исходники интерфейса Hunspell для Lazarus здесь: https://github.com/cutec-chris/hunspell


Mac

На Mac'е автора, по-видимому, была установлена библиотека Hunspell при установке Sierra. Но, может быть, просто возможно, это пришло вместе с Firefox. Я хотел бы получить обратную связь ....

Чтобы проверить, установлена ли у вас библиотека hunspell, попробуйте эту команду - find / 2>&1 | grep "\hunspell", она будет выполняться некоторое время, в зависимости от того, сколько файлов в вашей системе. Скорее всего, она найдет несколько файлов, включая некоторые, в вашем каталоге XCode. Однако конечным пользователям, вероятно, не будет установлен XCode. Один особенно интересный файл для меня был /usr/lib/libhunspell-1.2.dylib. Версия 1.2 немного старше, чем где-либо, но работала нормально.

Если вы не можете найти пригодную для использования библиотеку, я предлагаю вам установить ее с помощью brew, см. ссылку ниже.

Следующая проблема - вам понадобятся словари. Аналогичная команда, find / 2>&1 | grep "\.aff", опять же, медленная, она ищет по всему вашему диску. Я нашел [команду] /Applications/Firefox.app/Contents/Resources/dictionaries/en-US.aff. И быстрый 'ls' заверил меня, что есть соответствующие файлы en-US.dic, так что все хорошо.

Более опытный пользователь Mac может предложить лучшие стратегии поиска. Пожалуйста!

Модуль Hunspell

Demo 1 простая командная строка

Вот очень простое демо командной строки о том, как использовать hunspell.pas. К сожалению, это конкретное демо подходит только для Linux, как описано ниже. Сохраните этот блок кода как testhun.pas и сохраните hunspell.pas (ниже) в каталоге и введите следующую команду:

fpc -Fu/usr/share/lazarus/1.8.0/components/lazutils/lib/x86_64-linux -Fu. testhun.pas

Если вы используете 32-битную версию Linux или ваш Lazarus установлен «где-то еще», вам нужно настроить параметр в -Fu

program testhun;

{$mode objfpc}{$H+}

uses
    Classes, hunspell, sysutils;

var
  Spell : THunspell;
  Sts : TStringList;
  I : integer;

begin
    Spell := THunspell.Create();
    if Spell.ErrorMessage = '' then begin
      if Spell.SetDictionary('/usr/share/hunspell/en_US.dic') then begin
            writeln('speller ' + booltostr(Spell.Spell('speller'), True));
            writeln('badspeller ' + booltostr(Spell.Spell('badspeller'), True));
            Sts := TStringList.Create();
            Spell.Suggest('badspeller', Sts);
            for i := 0 to Sts.Count -1 do
                writeln('    ' + Sts.Strings[I]);
            Sts.Free;
      end else
        writeln('ERROR - Dictionary not loaded.');//[прим.перев.]: ОШИБКА - словари не найдены 
    end else writeln('ERROR - Library not loaded.');//[прим.перев.]: ОШИБКА - библиотека не найдена 
    Spell.Free;
end.

Почему именно эта демонстрационная версия [для] Linux? Модуль hunspell предназначен для приложений с графическим интерфейсом, он использует модуль под названием Forms, который не имеет смысла в приложении командной строки. В Windows и Mac методы Forms, Application.ExeName используется, чтобы определить, где находится бинарный файл, если вы поместили туда библиотеку hunspell (в Linux она имеет предопределенное место для существования).

Demo 2 в полном графическом интерфейсе

Демо графического интерфейса Lazarus имеет больше смысла и была протестирована на Linux, Mac и Windows. Но это немного сложнее, [чем] скопировать и вставить.

Для этой демо вам понадобится форма с двумя TMemo: Memo1 и MemoMsg. Кнопка ButtonSpell, Tlistbox Listbox1. Сделайте следующие обработчики событий: FormCreate для главной формы; дважды щелкните по Listbox1 и [дважды] кликните по ButtonSpell.

Сначала вы должны создать объект hunspell и посмотреть, нашел ли он свою библиотеку, вот пример метода FormCreate () ....

uses hunspell;
var
    Form1: TForm1;
    Sp: THunspell;
    DictPath : AnsiString;              

procedure TForm1.FormCreate(Sender: TObject);
begin
    SetDefaultDicPath();
    Sp := THunspell.Create();
    if Sp.ErrorMessage = '' then begin
        MemoMsg.append('Library Loaded =' + Sp.LibraryFullName);
        ButtonSpell.enabled := CheckForDict();
    end else
        MemoMsg.append(SP.ErrorMessage);
end;

В этом примере мы пишем сообщение о статусе в MemoMsg, это простой способ увидеть, что происходит. ButtonSpell НЕ включен, пока не будут установлены словари. Ожидаем этого ....

Теперь нам нужны два метода: один читает назначенный каталог и ищет возможные файлы словарей, другой управляет решениями. Если мы найдем только один словарный набор, используем его, если мы не найдем - пожалуемся. Но если мы найдем несколько, и это наиболее вероятно, мы должны спросить пользователя, какой словарь (то есть язык) он хочет использовать.

function TForm1.FindDictionary(const Dict : TStrings; const DPath : AnsiString) : boolean;
var
    Info : TSearchRec;
begin
    Dict.Clear;
    if FindFirst(AppendPathDelim(DPath) + '*.dic', faAnyFile and faDirectory, Info)=0 then begin
        repeat
            Dict.Add(Info.Name);
        until FindNext(Info) <> 0;
    end;
    FindClose(Info);
    Result := Dict.Count >= 1;
end;

function TForm1.CheckForDict() : boolean;
begin
    Result := False;
    EditDictPath.Caption := DictPathAlt;
    if not FindDictionary(ListBox1.Items, DictPath) then
        MemoMsg.Append('ERROR - no dictionaries found in ' + DictPath);//[прим.перев.]: словари в DictPath не найдены
    if ListBox1.Items.Count = 1 then begin                   // Один [словарь] вернулся точно.
        if not Sp.SetDictionary(AppendPathDelim(DictPath) + ListBox1.Items.Strings[0]) then
            MemoMsg.Append('ERROR ' + SP.ErrorMessage)
        else
            MemoMsg.Append('Dictionary set to ' + DictPath + ListBox1.Items.Strings[0]);// [прим.перев.]: словарь (первый из списка ListBox1) установлен в DictPath
    end;
    Result := SP.GoodToGo;   // если count был точно один или FindDict не вернул ничего и ничего не изменилось
end;

Ах, вы спросите, но где нам искать словари? К сожалению, у меня нет хорошего решения для этого. Вот где я нашел свой -

procedure TForm1.SetDefaultDicPath();
begin
    {$ifdef LINUX}
    DictPath := '/usr/share/hunspell/';
    {$ENDIF}
    {$ifdef WINDOWS}
    DictPath := ExtractFilePath(Application.ExeName);
    //DictPath := 'C:\Program Files\LibreOffice 5\share\extensions\dict-en\';
    {$ENDIF}
    {$ifdef DARWIN}
    DictPath := '/Applications/Firefox.app/Contents/Resources/dictionaries/';
    //DictPathAlt := ExtractFilePath(Application.ExeName);
    {$endif}
end;

Возможно, если другие пользователи сообщат, где они нашли пригодные для использования словари Hunspell, мы можем составить список для каждой платформы. Или просто выберите легкий путь и попросите пользователя найти несколько словарей и поместить их в каталог приложений на Windows и Mac. Ваши мысли очень приветствуются ....

Пока, если в указанном каталоге есть ровно один словарь, все хорошо. Но что, если их несколько? Наш ListBox1 содержит их список, если пользователь дважды щелкнет один из них, он вызовет этот метод -

procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
    if ListBox1.ItemIndex > -1 then
        ButtonSpell.enabled := Sp.SetDictionary( AppendPathDelim(DictPath) + ListBox1.Items.Strings[ListBox1.ItemIndex]);
    if SP.ErrorMessage = '' then begin
        MemoMsg.Append('Good To Go =' + booltostr(Sp.GoodToGo, True));
        MemoMsg.Append('Dictionary set to ' + AppendPathDelim(DictPath) + ListBox1.Items.Strings[ListBox1.ItemIndex]);
    end else
        MemoMsg.append('ERROR ' + SP.ErrorMessage);
end;

Предполагая, что у нас теперь есть все, что нужно, мы можем нажать кнопку ButtonSpell и вызвать это -

procedure TForm1.ButtonSpellClick(Sender: TObject);
begin
    if not Sp.Spell(Edit1.text) then begin
        Memo1.Lines.BeginUpdate;
        Sp.Suggest('badspeller', Memo1.lines);
        Memo1.Lines.EndUpdate;
    end else
        Memo1.Lines.Clear;
end;

Memo1 теперь содержит несколько советов о лучших способах написания [слова] неграмотному!

Важно! Не забудьте освободить наш объект hunspeller, утечки памяти - зло!

procedure TForm1.FormDestroy(Sender: TObject);
begin
    Sp.free;
    Sp := nil;
end;

Этот модуль делает значительно больше, но он представлен здесь в его наиболее урезанной форме для удобства чтения.

Note-icon.png

Примечание: для новичков в Lazarus, методы с "(Sender: TObject)", показанные выше, не могут быть просто вставлены в ваш исходник, сначала используйте инспектор объектов формы, чтобы создать события, а затем вставьте мой пример кода в метод.

Актуальный код

(извините, это действительно слишком много [текста], чтобы вставлять внутрь вики-страницы, но это, кажется, для меня единственный вариант)


Прим.перев.: далее по тексту для понимания содержимого модуля будут вставки на русском языке, которые отсутствуют в оригинале



{$MODE objfpc}{$H+}
unit hunspell;

{   Hunspell interface.
    Based on code that seems to appear in lots of places in the Lazarus Forum
    and elsewhere.

    With additions and corrections by dbannon to make it a little easier to use.

    As such, its assumed to be free to use by anyone for any purpose.
}

{   Интерфейс Hunspell.
    Основан на коде, который появляется во многих местах на форуме Lazarus.
    и в других местах.

    С дополнениями и исправлениями от dbannon, чтобы сделать его немного проще в использовании.

    Таким образом, предполагается, что он может свободно использоваться кем-либо для любых целей.
}

{   A Unit to connect to the hunspell library and check some spelling.
    First, create the class, it will try and find a library to load.
    Check ErrorMessage.
    Then call SetDictionary(), with a full filename of the dictionary to use.
    If GoodToGo is true, you can call Spell() and Suggests()
    otherwise, look in ErrorString for what went wrong.

    Look in FindLibrary() for default locations of Library.
}

{   Модуль для подключения к библиотеке hunspell и проверки орфографии.
    Сначала создайте класс, он попытается найти библиотеку для загрузки.
    Проверьте ErrorMessage.
    Затем вызовите SetDictionary(), указав полное имя словаря для использования.
    Если GoodToGo имеет значение true, вы можете вызвать Spell() и Suggests()
    в противном случае посмотрите в ErrorString, что пошло не так.

    Посмотрите в FindLibrary()[, чтобы узнать] местоположение библиотеки по умолчанию.
}


interface
uses Classes, dynlibs;

type
  THunspell_create = function(aff_file: PChar; dict_file: PChar): Pointer; cdecl;
  THunspell_destroy = procedure(spell: Pointer); cdecl;
  THunspell_spell = function(spell: Pointer; word: PChar): Boolean; cdecl;
  THunspell_suggest = function(spell: Pointer; out slst: PPChar; word: PChar): Integer; cdecl;
  THunspell_analyze = function(spell: Pointer; var slst: PPChar; word: PChar): Integer; cdecl;
  THunspell_stem = function(spell: Pointer; var slst: PPChar; word: PChar): Integer; cdecl;
  THunspell_free_list = procedure(spell: Pointer; var slst: PPChar; n: integer); cdecl;
  THunspell_get_dic_encoding = function(spell: Pointer): PChar; cdecl;
  THunspell_add = function(spell: Pointer; word: PChar): Integer; cdecl;
  THunspell_remove = function(spell: Pointer; word: PChar): Integer; cdecl;

   { THunspell }

  THunspell = class
  private
    Speller: Pointer;
        { Loads indicated library, returns False and sets ErrorMessage if something wrong }
        {Загружает указанную библиотеку, возвращает False и устанавливает ErrorMessage, если что-то не так}
    function LoadHunspellLibrary(LibraryName: AnsiString): Boolean;
  public
    	    { set to True if speller is ready to accept requests }
            {установить в True, если speller готов принимать запросы}
    GoodToGo : boolean;
    	    { empty if OK, contains an error message if something goes wrong }
            {пусто, если ОК, и содержит сообщение об ошибке, если что-то идет не так}
    ErrorMessage : ANSIString;
            { Will have a full name to library if correctly loaded at create }
            {Будет иметь полное имя для библиотеки, если правильно загружено при создании}
    LibraryFullName : string;
            { Will have a "first guess" as to where dictionaries are, poke another name in
            and call FindDictionary() if default did not work }
            {У вас будет «первое предположение» о том, где находятся словари, ткните другое имя в
            и вызовите FindDictionary(), если по умолчанию не работает}
    constructor Create();
    destructor Destroy; override;
            { Returns True if word spelt correctly }
            {Возвращает True, если слово написано правильно}
    function Spell(Word: string): boolean;
            { Returns with List full of suggestions how to spell Word }
            {Возвращается со списком, полным предложений, как пишется слово}
    procedure Suggest(Word: string; List: TStrings);
            { untested }
            {непроверенный}
    procedure Add(Word: string);
            { untested }
            {непроверенный}
    procedure Remove(Word: string);
            { returns a full library name or '' if it cannot find anything suitable }
            {возвращает полное имя библиотеки или пустую строку, если не может найти ничего подходящего}
    function FindLibrary(out FullName : AnsiString) : boolean;
            { returns true if it successfully set the indicated dictionary }
            {возвращает true, если успешно установлен указанный словарь}
    function SetDictionary(const FullDictName: string) : boolean;
    function SetNewLibrary(const LibName : string) : boolean;
  end;

var Hunspell_create: THunspell_create;
var Hunspell_destroy: THunspell_destroy;
var Hunspell_spell: Thunspell_spell;
var Hunspell_suggest: Thunspell_suggest;
var Hunspell_analyze: Thunspell_analyze;
var Hunspell_stem: Thunspell_stem;
var Hunspell_get_dic_encoding: Thunspell_get_dic_encoding;
var Hunspell_add: THunspell_add;
var Hunspell_free_list: THunspell_free_list;
var Hunspell_remove: THunspell_remove;

var HunLibLoaded: Boolean = False;
var HunLibHandle: THandle;

implementation

uses LazUTF8, SysUtils, {$ifdef linux}Process, {$else} Forms, {$endif} LazFileUtils;
// Нужны формы, чтобы мы могли вызвать Application.~

{ THunspell }

function THunspell.LoadHunspellLibrary(libraryName: Ansistring): Boolean;
begin
    Result := false;
    HunLibHandle := LoadLibrary(PAnsiChar(libraryName));
    if HunLibHandle = NilHandle then
        ErrorMessage := 'Failed to load library ' + libraryName
    else begin
        Result := True;
        Hunspell_create := THunspell_create(GetProcAddress(HunLibHandle, 'Hunspell_create'));
        if not Assigned(Hunspell_create) then Result := False;
        Hunspell_destroy := Thunspell_destroy(GetProcAddress(HunLibHandle, 'Hunspell_destroy'));
        if not Assigned(Hunspell_destroy) then Result := False;
        Hunspell_spell := THunspell_spell(GetProcAddress(HunLibHandle, 'Hunspell_spell'));
        if not Assigned(Hunspell_spell) then Result := False;
        Hunspell_suggest := THunspell_suggest(GetProcAddress(HunLibHandle, 'Hunspell_suggest'));
        if not Assigned(Hunspell_suggest) then Result := False;
        Hunspell_analyze := THunspell_analyze(GetProcAddress(HunLibHandle, 'Hunspell_analyze'));  // здесь не используется
        if not Assigned(Hunspell_analyze) then Result := False;
        Hunspell_stem := THunspell_stem(GetProcAddress(HunLibHandle, 'Hunspell_stem'));           // здесь не используется
        if not Assigned(Hunspell_stem) then Result := False;
        Hunspell_get_dic_encoding := THunspell_get_dic_encoding(GetProcAddress(HunLibHandle, 'Hunspell_get_dic_encoding'));   // здесь не используется
        if not Assigned(Hunspell_get_dic_encoding) then Result := False;
        Hunspell_free_list := THunspell_free_list(GetProcAddress(HunLibHandle, 'Hunspell_free_list'));
        if not Assigned(Hunspell_free_list) then Result := False;
        Hunspell_add := THunspell_add(GetProcAddress(HunLibHandle, 'Hunspell_add'));
        if not Assigned(Hunspell_add) then Result := False;
        Hunspell_remove := THunspell_remove(GetProcAddress(HunLibHandle, 'Hunspell_remove'));
        if not Assigned(Hunspell_remove) then Result := False;
        HunLibLoaded := Result;
    end;
    if ErrorMessage = '' then
        if not Result then ErrorMessage := 'Failed to find functions in ' + LibraryName;
end;

constructor THunspell.Create();
begin
    ErrorMessage := '';
    if Not FindLibrary(LibraryFullName) then begin
        ErrorMessage := 'Cannot find Hunspell library';
        exit();
    end;
    LoadHunspellLibrary(LibraryFullName);    // отметит все найденные ошибки
    Speller := nil;           // мы еще не GoodToGo, нужен словарь ....
end;

destructor THunspell.Destroy;
begin
    if (HunLibHandle <> 0) and HunLibLoaded then begin
        if Speller<>nil then hunspell_destroy(Speller);
        Speller:=nil;
        if HunLibHandle <> 0 then FreeLibrary(HunLibHandle);
        HunLibLoaded := false;
    end;
    inherited Destroy;
end;

function THunspell.Spell(Word: string): boolean;
begin
    Result := hunspell_spell(Speller, PChar(Word))
end;

procedure THunspell.Suggest(Word: string; List: TStrings);
var i, len: Integer;
	SugList, Words: PPChar;
begin
    List.clear;
    try
        len := hunspell_suggest(Speller, SugList, PChar(Word));
        Words := SugList;
        for i := 1 to len do begin
            List.Add(Words^);
            Inc(PtrInt(Words), sizeOf(Pointer));
        end;
    finally
        Hunspell_free_list(Speller, SugList, len);
    end;
end;

procedure THunspell.Add(Word: string);
begin
    Hunspell_add(Speller, Pchar(Word));
end;

procedure THunspell.Remove(Word: string);
begin
    Hunspell_remove(Speller, Pchar(Word));
end;

function THunspell.FindLibrary(out FullName : ANSIString):boolean;
var
    {$ifdef LINUX} I : integer = 1; {$endif}
    Info : TSearchRec;
    Mask : ANSIString;
begin
    Result := False;
    {$IFDEF LINUX}
    // Предполагается, что ldconfig всегда возвращает один и тот же формат, лучше, чем поиск по нескольким директориям
    if RunCommand('/bin/bash',['-c','ldconfig -p | grep hunspell'], FullName) then begin
        while UTF8Pos(' ', FullName, I) <> 0 do inc(I);
        if I=1 then exit();
        UTF8Delete(FullName, 1, I-1);
        UTF8Delete(FullName, UTF8Pos(#10, FullName, 1), 1);
        Result := True;
    end;
    exit();
    {$ENDIF}
    {$IFDEF WINDOWS}		// Ищем dll в домашнем каталоге приложения.
    Mask := '*hunspell*.dll';
    FullName := ExtractFilePath(Application.ExeName);
    {$endif}
    {$ifdef DARWIN}
    Mask := 'libhunspell*';
    FullName := '/usr/lib/';
    {$endif}
    if FindFirst(FullName + Mask, faAnyFile and faDirectory, Info)=0 then begin
        FullName := FullName + Info.name;
        Result := True;
    end;
    FindClose(Info);
end;

function THunspell.SetDictionary(const FullDictName: string) : boolean;
var
    FullAff : string;
begin
    FullAff := FullDictName;
    UTF8Delete(FullAff, UTF8Length(FullAff) - 2, 3);
    FullAff := FullAff + 'aff';
    if Speller <> Nil then
        hunspell_destroy(Speller);
    Speller := hunspell_create(PChar(FullAff), PChar(FullDictName));
    GoodToGo := Speller <> Nil;
    if not GoodToGo then
        ErrorMessage := 'Failed to set Dictionary ' + FullDictName;
    Result := GoodToGo;
end;

function THunspell.SetNewLibrary(const LibName: string): boolean;
begin
    LibraryFullName := LibName;
    Result := LoadHunspellLibrary(LibraryFullName);
end;

end.

Обратите внимание, что этот модуль использует LazUTF8, LazFileUtils и Forms. Если вы хотите использовать его в качестве простого приложения командной строки, вы можете добавить LCL в обязательные пакеты в Инспекторе проектов или вернуться к FPC-версиям Pos() и т.д., но в ущерб совместимости с UTF8. И вы не можете использовать [модуль] Forms для предоставления Application.ExeName.

Дальнейшее чтение и ссылки

https://github.com/hunspell/hunspell

https://github.com/Homebrew - вероятно, разумный способ получить Hunspell на вашем Mac, если его еще нет.

https://github.com/tomboy-notes/tomboy-ng/releases - содержит 64-битную Windows DLL в tomboy-ng_win64_<ver>.zip

https://github.com/cutec-chris/hunspell