Sign in Join
Wiki copytemporary

spelling: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Using Hunspell with Lazarus
22
33> **Note:** This page is current as of August, 2018 but things change...
44
55This page is about using the Hunspell library with Lazarus. It outlines a model that does, sort of, work. You will almost certainly need to make some changes for your specific purposes but hopefully, this page will give you a good start.
66
77Firstly, in the forum, there are several references to some code that will work with the Hunspell library. The hunspell.pas unit is based heavily on these blocks of code. Most have no license information and an assumption is being made that they are "common knowledge" and thus free of any constraint. I have added a bit that manages the problem of finding the library and dictionary files. And established a reasonable interface.
88
99Further, user **rvk** from the forum has build a windows 64 bit DLL as there seemed little alternative for Windows users.
1010
1111## About Hunspell
1212
1313Hunspell is an active open source project, distributed under the Mozilla Public License. The hunspell library is used in products like Libra Office, Open Office and Firefox. Its can be made work on Windows, Linux and Mac (and probably heaps of other platforms). See the platform Specific pages below. Dictionaries for Hunspell are readily available and may already be installed on many machines. Even if you cannot access another application's library, you can use its dictionary.
1414
1515Dictionaries for Hunspell come as a *.dic and *.aff file pair. For example, the Australian dictionary consists of en_AU.dic and en_AU.aff . The 'en' indicates its english and the 'AU' says its specifically for Australia. As an English speaker, I note that the en_US dictionaries always seem to be installed and I add the Australian ones. I don't know how widespread this pattern is in non English speaking systems. You can find dictionaries here <https://github.com/LibreOffice/dictionaries> and also some information related to it here <https://wiki.documentfoundation.org/Development/Dictionaries>
1616
1717## Note about dictionaries
1818
1919In some cases in order to display the suggested words correctly the dictionaries must be encoded in UTF-8. If you have notice that some words are not shown correctly--for example, 'apple' in Polish is 'jabłko' but you get 'jab�ko'--it means that dictionary pl_PL.aff and pl_PL.dic must be converted in UTF-8. To convert dictionaries into UTF-8 in Linux Debian (it should work in any Linux, converted dictionaries can be used in Windows, check also [this forum thread](http://forum.lazarus.freepascal.org/index.php/topic,44298.0.html)) follow below steps:
2020
21211\. Start terminal as root
2222
23-`  su`
23+```text
24+ su
25+```
2426
25272\. If not yet installed, install hunspell (hunspell-pl is for Polish dictionary--more at [Debian Hunspell package](https://packages.debian.org/search?keywords=hunspell))
2628
27-`  apt-get install hunspell`\
28-`  apt-get install hunspell-pl`
29+```text
30+ apt-get install hunspell
31+ apt-get install hunspell-pl
32+```
2933
30343\. Go to /usr/share/hunspell/ where dictionaries are kept and create folder dic
3135
32-`  cd /usr/share/hunspell/`\
33-`  mkdir dic`
36+```text
37+ cd /usr/share/hunspell/
38+ mkdir dic
39+```
3440
35414\. Convert for example dictionary pl_PL.aff and pl_PL.dic. Use [ISO-8859-2](https://en.wikipedia.org/wiki/ISO/IEC_8859-2) (for Eastern Europe), [ISO-8859-1](https://en.wikipedia.org/wiki/ISO/IEC_8859-1) (or ISO-8859-15) for Western Europe, ISO-8859-5 for (Russian?), etc.
3642
37-`  iconv -f ISO-8859-2 -t UTF-8 /usr/share/hunspell/pl_PL.aff | sed 's/^SET ISO8859-2$/SET UTF-8/g' > dic/pl_PL.aff`\
38-`  iconv -f ISO-8859-2 -t UTF-8 /usr/share/hunspell/pl_PL.dic > dic/pl_PL.dic`
43+```text
44+ iconv -f ISO-8859-2 -t UTF-8 /usr/share/hunspell/pl_PL.aff | sed 's/^SET ISO8859-2$/SET UTF-8/g' > dic/pl_PL.aff
45+ iconv -f ISO-8859-2 -t UTF-8 /usr/share/hunspell/pl_PL.dic > dic/pl_PL.dic
46+```
3947
40485\. Copy your 'dic' folder or dictionaries from 'dic' folder to your application.
4149
4250## Platform Specific
4351
4452### Linux
4553
4654Many Linux distributions will have Hunspell installed by default along with the appropriate language dictionaries. If not, its probably just a case of using the distribution's package manager. If all else fails, grab the source from the hunspell GitHub site and build it yourself. Linux users are like that.
4755
4856To see if you do in fact have a hunspell library installed, try this command.
4957
50-` ldconfig -p | grep hunspell`
58+```text
59+ ldconfig -p | grep hunspell
60+```
5161
5262Similarly, you can probably find some dictionaries with
5363
54-` ls -l /usr/share/hunspell`
64+```text
65+ ls -l /usr/share/hunspell
66+```
5567
5668If that does not work, try
5769
58-` find /usr -name *.aff`
70+```text
71+ find /usr -name *.aff
72+```
5973
6074It will take a bit longer...
6175
6276### Windows
6377
6478Installing the hunspell library on Windows is more of an issue. There is apparently no pre compiled 'kit' available and most Windows apps that use Hunspell appear to bind it staticly thus no hunspell.dll left lying around for you to use. But just to be sure, try searching for `*hunspell*.dll`. The Hunspell GitHub site lists a recipe to build one but it involves installing MSYS2 and is quite involved. The resulting DLL also needs a couple of GCC DLLs along as well.
6579
6680Fortunately user **rvk** on the Lazarus Forum has built us a nice statically (ie stand alone) linked DLL using Microsoft Visual Studio Community 2015. As such, you can use and distribute this DLL with your program subject to the Mozilla Public License.
6781
6882You will find this DLL bundled with its license file in <https://github.com/davidbannon/hunspell4pas>, click 'DLL', click the dll file, and you will get a 'Download' button. Don't forget to also get the license file, it should be distributed, one way or another with your app.
6983
7084### macOS
7185
7286The author's Mac appears to have had the Hunspell library installed when Sierra was installed. But maybe, just maybe, it came along with Firefox. I'd like some feed back .... \[already installed on Mojave and Catalina\]
7387
7488> **Note:** See <https://opensource.apple.com/tarballs/hunspell/> for source download for v1.2.8.
7589
7690To see if you aleady have a hunspell library installed, try this command
7791
78-` find / 2>&1 | grep "\hunspell"`
92+```text
93+ find / 2>&1 | grep "\hunspell"
94+```
7995
8096It will run for some time, depending on how many files on your system. It will likely find several files including some in your XCode directory. However, end users will probably not have XCode installed. The one particularly interesting file to me was
8197
82-` /usr/lib/libhunspell-1.2.dylib`
98+```text
99+ /usr/lib/libhunspell-1.2.dylib
100+```
83101
84102Version 1.2 is a bit older than elsewhere but it worked fine. If you don't find a usable library, I suggest you install one using a package manager like MacPorts, Fink or brew. Next issue issue is you will need some dictionaries. Similar command,
85103
86-` find / 2>&1 | grep "\.aff"`
104+```text
105+ find / 2>&1 | grep "\.aff"
106+```
87107
88108again, slow, it searches your whole disk. I found
89109
90-` /Applications/Firefox.app/Contents/Resources/dictionaries/en-US.aff`
110+```text
111+ /Applications/Firefox.app/Contents/Resources/dictionaries/en-US.aff
112+```
91113
92114And a quick 'ls' assured me that there was a matching 'en-US.dic' files so all good. A more experienced Mac user might like to suggest better search strategies. Please !
93115
94116## The Hunspell Unit
95117
96118When this page was initially created (2017 ?) I made a serious mistake and used some code that did not have a clear license or history. It has been suggested that by doing so, I breached the Hunspell license as has anyone who has used it since then.
97119
98120To correct this situation, I have now released a new version of the Hunspell Wrapper that has a quite small part, just the function definitions, with the Hunspell (License: MPL 1.1/GPL 2.0/LGPL 2.1) and the Hunspell Unit (License: The Clear BSD License) in seperate files. From a coding point of view, its almost identical to what used to be here.
99121
100122Please see <https://github.com/davidbannon/hunspell4pas>
101123
102124I have removed the hunspell wrapper that used to live on this page. The one on Github above is a drop in replacement, you need hunspell.pas and hunspell.inc. The remainder of the file make a basic test/demo command line Lazarus project that replaces Demo 1.
103125
104126### Demo 2 in a Full GUI
105127
106128A Lazarus GUI demo makes a lot more sense and has been tested on Linux, Mac and Windows. But it is a bit harder to copy and paste.
107129
108130For this demo, you'll need a form with two TMemos, Memo1 and MemoMsg. A button, ButtonSpell, a Tlistbox, Listbox1. Make the following event handlers : FormCreate for the main form; a double click for the listbox and click on the Button.
109131
110132First you must create the hunspell object and see if it found a library its own way, here is an example in the FormCreate() method ....
111133
112134``` pascal
113135uses hunspell; // get hunspell.pas and hunspell.inc from https://github.com/davidbannon/hunspell4pas
114136var
115137 Form1: TForm1;
116138 Sp: THunspell;
117139 DictPath : AnsiString;
118140
119141procedure TForm1.FormCreate(Sender: TObject);
120142begin
121143 SetDefaultDicPath();
122144 Sp := THunspell.Create(True);
123145 if Sp.ErrorMessage = '' then begin
124146 MemoMsg.append('Library Loaded =' + Sp.LibraryFullName);
125147 ButtonSpell.enabled := CheckForDict();
126148 end else
127149 MemoMsg.append(SP.ErrorMessage);
128150end;
129151```
130152
131153In this example, we are writing some status message to MemoMsg, its an easy way to see whats happening. The ButtonSpell is NOT enabled until the dictionaries have been set. Wait for it ....
132154
133155We now need two methods, one reads a nominated directory looking for any likely dictionary files, the other manages the decisions. If we find just one dictionary set, use it, if we find none, complain. But if we find several, and thats most likely, we have to ask the user which dictionary (ie language) they wish to use.
134156
135157``` pascal
136158function TForm1.FindDictionary(const Dict : TStrings; const DPath : AnsiString) : boolean;
137159var
138160 Info : TSearchRec;
139161begin
140162 Dict.Clear;
141163 if FindFirst(AppendPathDelim(DPath) + '*.dic', faAnyFile and faDirectory, Info)=0 then begin
142164 repeat
143165 Dict.Add(Info.Name);
144166 until FindNext(Info) <> 0;
145167 end;
146168 FindClose(Info);
147169 Result := Dict.Count >= 1;
148170end;
149171
150172function TForm1.CheckForDict() : boolean;
151173begin
152174 Result := False;
153175 EditDictPath.Caption := DictPathAlt;
154176 if not FindDictionary(ListBox1.Items, DictPath) then
155177 MemoMsg.Append('ERROR - no dictionaries found in ' + DictPath);
156178 if ListBox1.Items.Count = 1 then begin // Exactly one returned.
157179 if not Sp.SetDictionary( AppendPathDelim(DictPath) +
158180 ListBox1.Items.Strings[0])
159181 then
160182 MemoMsg.Append('ERROR ' + SP.ErrorMessage)
161183 else
162184 MemoMsg.Append('Dictionary set to ' + DictPath +
163185 ListBox1.Items.Strings[0]);
164186 end;
165187 Result := SP.GoodToGo; // only true if count was exactly one or FindDict failed and nothing changed
166188end;
167189```
168190
169191Ah, you say, but where are we looking for the dictionaries ? Sadly, I don't have a good solution for that. Here is where I found mine -
170192
171193``` pascal
172194procedure TForm1.SetDefaultDicPath();
173195begin
174196 {$ifdef LINUX}
175197 DictPath := '/usr/share/hunspell/';
176198 {$ENDIF}
177199 {$ifdef WINDOWS}
178200 DictPath := ExtractFilePath(Application.ExeName);
179201 //DictPath := 'C:\Program Files\LibreOffice 5\share\extensions\dict-en\';
180202 {$ENDIF}
181203 {$ifdef DARWIN}
182204 DictPath := '/Applications/Firefox.app/Contents/Resources/dictionaries/';
183205 //DictPathAlt := ExtractFilePath(Application.ExeName);
184206 {$endif}
185207end;
186208```
187209
188210Maybe, if other users contribute where they found usable hunspell dictionaries, we can build a list for each platform. Or just take the easy way out and tell user to get some dictionaries and put them into the application directory on Windows and Mac. Your thoughts very welcome....
189211
190212So far, if there is exactly one dictionary set in the indicated directory, all good. But what if there are several ? Our list box has a list of them, if the user double clicks one, they trigger this method -
191213
192214``` pascal
193215procedure TForm1.ListBox1DblClick(Sender: TObject);
194216begin
195217 if ListBox1.ItemIndex > -1 then
196218 ButtonSpell.enabled := Sp.SetDictionary( AppendPathDelim(DictPath) +
197219 ListBox1.Items.Strings[ListBox1.ItemIndex]);
198220 if SP.ErrorMessage = '' then begin
199221 MemoMsg.Append('Good To Go =' + booltostr(Sp.GoodToGo, True));
200222 MemoMsg.Append('Dictionary set to ' + AppendPathDelim(DictPath) +
201223 ListBox1.Items.Strings[ListBox1.ItemIndex]);
202224 end else
203225 MemoMsg.append('ERROR ' + SP.ErrorMessage);
204226end;
205227```
206228
207229Assuming we now have everything "good to go" we can press the Spell button and trigger this -
208230
209231``` pascal
210232procedure TForm1.ButtonSpellClick(Sender: TObject);
211233begin
212234 if not Sp.Spell(Edit1.text) then begin
213235 Memo1.Lines.BeginUpdate;
214236 Sp.Suggest('badspeller', Memo1.lines);
215237 Memo1.Lines.EndUpdate;
216238 end else
217239 Memo1.Lines.Clear;
218240end;
219241```
220242
221243Memo1 now contains some suggestions for better ways to spell badspeller !
222244
223245Important ! Don't forget to free up our hunspeller object, memory leaks are evil !
224246
225247``` pascal
226248procedure TForm1.FormDestroy(Sender: TObject);
227249begin
228250 Sp.free;
229251 Sp := nil;
230252end;
231253```
232254
233255This module will do considerably more but its presented here in its most stripped down form for readability.
234256
235257*A note to people new to Lazarus, methods with "(Sender: TObject)" shown above cannot just be pasted into your source, use the Form's Object Inspector to create the events first, then paste my sample code into the method.*
236258
237259### Demo 3 - Lazspell - TMemo
238260
239261In this example TMemo component was used.
240262
241263![](/wiki-images/b/b3/Lazspell.gif)
242264
243265Lazspell - an example of spelling checker - from <https://github.com/Raf20076/Lazspell>
244266
245267### Demo 4 - Lazspell 2 - TRichMemo
246268
247269In this example TRichMemo component was used.
248270
249271![](/wiki-images/8/8c/Lazspell-ver2.gif)
250272
251273Lazspell 2 version 2 - an example of spelling checker - from <https://github.com/Raf20076/Lazspell-2-version-2>
252274
253275### Demo 5 - Simple spelling checker
254276
2552771\. Start Lazarus IDE
256278
2572792\. Click Project -\> New Project -\> Choose -\> Application
258280
259-`  You have just created a new application. Now save it `
281+```text
282+ You have just created a new application. Now save it
283+```
260284
2612853\. Click File -\> Save as
262286
263-`  Choose a folder where your application will be saved`\
264-`  First, project1.lpi will be saved, then unit1.pas`
287+```text
288+ Choose a folder where your application will be saved
289+ First, project1.lpi will be saved, then unit1.pas
290+```
265291
2662924\. Place hunspell.pas and hunspell.inc (from <https://github.com/davidbannon/hunspell4pas>) in your folder.
267293
2682945\. Place libhunspell.dll in your application folder (you can download it from here <https://github.com/Raf20076/Lazspell> or <https://github.com/davidbannon/hunspell4pas>) any options or compile by yourself.
269295
2702966\. Place dictionary in your application folder, (two files) like pl_PL.aff, pl_PL.dic. Dictionaries must be encoded in UTF8. You can download them from here <https://github.com/Raf20076/Lazspell/tree/master/dict>
271297
2722987\. On Form1 place component TButton (Button1) from Standard Tab
273299
2743008\. On Form1 place component TMemo (Memo1) from Standard Tab
275301
2763029\. On Form1 place component TListbox (ListBox1) from Standard Tab (here misspell words will be shown)
277303
27830410\. Click once Button1 on Form1 then go to Object Inspector, click Events tab, and double click next to OnClick event: it creates OnClick event and paste code (see the code of the whole application) starts from
279305
280306``` pascal
281307
282308{Check spelling}
283309 var
284310 i : Integer;
285311 MAX : Integer;
286312 FillInArrayWithWords:TStringArray;
287313 FillInString1: String;
288314 FillInString2: String;
289315```
290316
291317till
292318
293319``` pascal
294320
295321//If word is not in dictionary show it in Listbox as an error
296322```
297323
29832411\. Go to Object Inspector and click Form1, then click Events tab then double click next to OnCreate it creates OnCreate event and paste code (see the code of the whole application)
299325
300326``` pascal
301327 SpellCheck := THunspell.Create(True);
302328 SpellCheck.SetDictionary('pl_PL.dic');//Load dictionary
303329 SpellCheck.GoodToGo := True;
304330```
305331
30633212\. Still in ObjectInspector click Form1,then click Events tab then double click next to OnDestroy it creates OnDestroy event and paste code (see the code of the whole application)
307333
308-` SpellCheck.free;`\
309-` SpellCheck := nil;`
334+``` pascal
335+ SpellCheck.free;
336+ SpellCheck := nil;
337+```
310338
31133913\. Then in your code place functions like ArrayValueCount etc
312340
313341See Below the code of the whole application to compare with your one.
314342
315343``` pascal
316344//SpellChecker by Raf20076, Poland 2019
317345
318346unit Unit1;
319347
320348{$mode objfpc}{$H+}
321349
322350interface
323351
324352uses
325353 Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
326354 LazFileUtils, LCLProc, LazUtils, LazUtf8;
327355
328356type
329357 TForm1 = class(TForm)
330358 Button1: TButton;
331359 Label1: TLabel;
332360 Label2: TLabel;
333361 ListBox1: TListBox;
334362 ListBox2: TListBox;
335363 Memo1: TMemo;
336364 procedure Button1Click(Sender: TObject);
337365 procedure FormCreate(Sender: TObject);
338366 procedure FormDestroy(Sender: TObject);
339367 end;
340368
341369var
342370 Form1: TForm1;
343371
344372implementation
345373
346374uses hunspell;//place hunspell.pas and hunspell .inc in your application folder
347375 //from https://github.com/davidbannon/hunspell4pas
348376
349377var
350378 SpellCheck: THunspell;
351379
352380{$R *.lfm}
353381
354382procedure TForm1.FormCreate(Sender: TObject);
355383begin
356384 SpellCheck := THunspell.Create(True);
357385 SpellCheck.SetDictionary('pl_PL.dic');//Load dictionary
358386 SpellCheck.GoodToGo := True;
359387end;
360388
361389{Extract words from string: non characters, spaces, carriagereturn aware}
362390
363391function ArrayValueCount(const InputArray: Array of string): Integer;
364392{Count elements in array}
365393var
366394 i:Integer;
367395begin
368396 result := 0;
369397 for i := low(InputArray) to high(InputArray) do
370398 if InputArray[i] <> ' ' then // 'between them one space'
371399 inc(result);
372400end;
373401
374402function StripOffNonCharacter(const aString: string): string;
375403{Remove non characters from string}
376404var
377405 a: Char;
378406begin
379407 Result := '';
380408 for a in aString do begin //below punctuation marks, numbers to remove from string
381409 if not CharInSet( a, ['.', ',', ';', ':', '!', '/',
382410 '?', '@', '#', '$', '%', '&', '*', '(', ')', '{',
383411 '}', '[', ']', '-', '\', '|', '<', '>', '''', '"', '^',
384412 '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '_', '+',
385413 '=', '~']) then //'„', '”'])these marks make error: Ordinal expression expected, need to be fixed
386414 begin
387415 Result := Result + a;
388416 end;
389417 end;
390418end;
391419
392420function ReplaceCarriageReturn(s: string) : string;
393421{Replace carriagereturn with one space}
394422var
395423 i: Integer;
396424begin
397425 Result:=s;
398426 for i := 1 to Length(Result) do
399427 if Result[i] in [#3..#13] then
400428 Result[i] := ' ';//'Between them one space'
401429end;
402430
403431procedure TForm1.Button1Click(Sender: TObject);
404432{Check spelling}
405433var
406434 i : Integer;
407435 MAX : Integer;
408436 FillInArrayWithWords:TStringArray;
409437 FillInString1: String;
410438 FillInString2: String;
411439begin
412440 ListBox1.clear;
413441 ListBox1.Items.Clear;
414442
415443 FillInString1 := ReplaceCarriageReturn(Memo1.Lines.Text);//take text from Memo1; replace carriage return
416444 //with one space (using ReplaceCarriageReturn function) and put into FillInString1
417445
418446 FillInString2 := StripOffNonCharacter(FillInString1);//remove all non characters from FillInString1
419447 //(using StripOffNonCharacter function) and put string without non charachters into FillInString2
420448
421449 FillInArrayWithWords := FillInString2.split(' '); //Split string into words with ' ' one space
422450 //(using .split) and put separate words into array FillInArrayWithWords
423451
424452 MAX := ArrayValueCount(FillInArrayWithWords); //Give how many elements are in array
425453 //(using function ArrayValueCount)
426454
427455 for i := 0 to MAX -1 do
428456 if not SpellCheck.Spell(FillInArrayWithWords[i]) then
429457 ListBox1.Items.add(FillInArrayWithWords[i]);
430458 //Take word from array and check in dictionary through hunspell (using SpellCheck.Spell function)
431459 //If word is not in dictionary show it in Listbox as an error
432460end;
433461
434462procedure TForm1.FormDestroy(Sender: TObject);
435463begin
436464 SpellCheck.free;
437465 SpellCheck := nil;
438466end;
439467
440468end.
441469```
442470
443471When you run your application and type some text in Memo and then press the button, the application will check if any errors are found, if found then shows them in ListBox. The main problem was how to remove carriage return and non characters and then how to split text into separate words. It's not an easy task. Therefore there are three functions to do this:ReplaceCarriageReturn function, StripOffNonCharacter function and FillInString2.split , function. The FillInString2.split function actually uses .split function from SysUtils. Hopefully the code of the whole application is self explanatory.
444472
445473## Further Reading and Links
446474
447475- <https://github.com/hunspell/hunspell>
448476- <https://github.com/davidbannon/hunspell4pas> - the (correctly licensed) hunspell object pascal unit.
449477- <https://github.com/Homebrew> - probably sensible way to get hunspell on your Mac if it's not already there.
450478- <https://github.com/tomboy-notes/tomboy-ng/releases> - Contains The 64bit Windows DLL in a tomboy-ng_win64_\<ver\>.zip
451479- <https://github.com/cutec-chris/hunspell>
452480- <https://forum.lazarus.freepascal.org/index.php/topic,50474.0.html> - FastHighliter code to use for highlighting misspelt words
453481- <https://github.com/Raf20076> - Lazspell is an example of simple spelling checker application written in Lazarus IDE.
454482- <https://forum.lazarus.freepascal.org/index.php/topic,46233.0.html>
455483- <https://forum.lazarus.freepascal.org/index.php/topic,51569.0.html>
456484- <https://forum.lazarus.freepascal.org/index.php?topic=44298.0>
457485
458486---
459487*From the Free Pascal wiki, page "spelling", as it stood on 2022-10-04 after a change by Alextp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history