Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2016-06

Encoding agnostic functions for codepoints + an iterator

8 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Juha Manninen

Hello

I have made an experimental unit that implements functions dealing with Unicode codepoints transparently for both UTF-8 and UTF-16 encodings as promised here:
http://wiki.freepascal.org/Better_Unicode_Support_in_Lazarus#Helper_functions_for_CodePoints

They use "String" type which maps to AnsiString with the Lazarus default UTF-8 system, and UnicodeString when {$ModeSwitch UnicodeStrings} is defined.

There is also a string iterator which the compiler can use for its for-in loop. As a result, regardless of encoding, this code works:

  for ch in s do
    writeln('ch=',ch);

Cool, ha?

To test it, extract the package, open the project in Lazarus, compile and run. Change the encoding with "UseUTF16" define in Custom Options page.

The unit LazCodePoint depends on LazUtils package, LazUTF8 and LazUTF16 units. Later it can be moved to LazUtils itself.
It implements some UTF-16 functions which can be moved to LazUTF16 unless FPC project provides them (as it should).
The test program is a cmd line program and has no other dependencies.

Issues:
1. How to use it without the "UseUTF16" define? The iterator does not compile in Mode DelphiUnicode.
2. How to implement an iterator for Unicode glyps + decomposed accented characters? It is the most complex part of Unicode. Has anybody made such code?

Juha
-------------- next part --------------
A non-text attachment was scrubbed...
Name: CodePointPub.tar.gz
Type: application/x-gzip
Size: 3280 bytes
Desc: not available
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20160620/be5e36b8/attachment.bin>

Reply by email

Bart
to Juha Manninen
Juha Manninen wrote
2. How to implement an iterator for Unicode glyps + decomposed accented characters? It is the most complex part of Unicode. Has anybody made such code?

Maybe Theo's UTF8Scanner implements this?

Bart

Reply by email

Bart
to Juha Manninen: Hello I have made an experimental unit that implements functions dealing with Unicode…
  i := CodePointLength(s);             // Should return 26.
  writeln('Testing CodePointLength. Result = ', i);

gives
Using UnicodeString + UTF-16
Sizeof(Char) = 2
...
Testing CodePointLength. Result = 30

Mind you that on my system the Eyes, Wineglass and Heart are unreadible inside Lazarus IDE (they are presented as an outline of a square block).

Win7 32 bit, fpc 3.0.0 Lazarus trunk.

A tip: put the -dUseUtf16 in a seperate buildmode?

Bart

Reply by email

Juha Manninen
to Bart
Bart wrote
i := CodePointLength(s); // Should return 26.

Ok, the source is wrong. I had changed the string after the comment. It should be:

  i := CodePointLength(s);             // Should return 30.
A tip: put the -dUseUtf16 in a seperate buildmode?

Yes, a good idea. If I make a new version, I will include that.

About Theo's UTF8 Tools and UTF8Scanner. I understand they work with CodePoints only.

Juha

Reply by email

Juha Manninen
to Bart: i := CodePointLength(s); // Should return 26. writeln('Testing…
Bart wrote 5 lines
Mind you that on my system the Eyes, Wineglass and Heart are unreadible inside Lazarus IDE (they are presented as an outline of a square block). Win7 32 bit, fpc 3.0.0 Lazarus trunk.

On my Linux Manjaro + KDE they show rather correctly. Anyway, the example code can deal with them even if a font does not support them.
It makes no difference for the numbers shown.

Juha

Reply by email

Martin Frb
to Juha Manninen: Hello I have made an experimental unit that implements functions dealing with Unicode…
Juha Manninen wrote
2. How to implement an iterator for Unicode glyps + decomposed accented characters? It is the most complex part of Unicode. Has anybody made such code?

Glyph and (de-)composed are different things

A glyph may be a ligature https://en.wikipedia.org/wiki/Typographic_ligature 2 or more chars, depending on the font you use. Even whole words.

As for (de-)composed: SynEdit has some code to detect them (combining codepoints). Not sure if (still) complete (if new ones were added ??). But if you just want (de-)composed, and not a complete unicode library (with all the properties for each codepoint), then feel free to look at the code, and use/copy it (that is: check svn blame, if any one else committed to that particular code. If it is just I, who committed, then feel free to copy, even if license changes.

Reply by email

Juha Manninen
to Martin Frb
Martin Frb wrote
Glyph and (de-)composed are different things A glyph may be a ligature https://en.wikipedia.org/wiki/Typographic_ligature 2 or more chars, depending on the font you use. Even whole words.

I don't know if glyphs require extra treatment. I guess I still don't understand all details.
Anyway, I try to figure out all Combining Diacritical Marks now.

As for (de-)composed: SynEdit has some code to detect them (combining codepoints). Not sure if (still) complete (if new ones were added ??). But if you just want (de-)composed, and not a complete unicode library (with all the properties for each codepoint), then feel free to look at the code, ...

I found:
function TSynEditStringList.LogicPosIsCombining(const AChar: PChar): Boolean; begin

   Result := (
    ( (AChar[0] = #$CC) ) or
            // Combining Diacritical Marks (belongs to previos char)

0300-036F
( (AChar[0] = #$CD) and (AChar[1] in [#$80..#$AF]) ) or

            // Combining Diacritical Marks
    ( (AChar[0] = #$D8) and (AChar[1] in [#$90..#$9A]) ) or
            // Arabic 0610 (d890)..061A (d89a)
    ( (AChar[0] = #$D9) and (AChar[1] in [#$8b..#$9f, #$B0]) ) or
            // Arabic 064B (d98b)..065F (d99f) // 0670 (d9b0)
    ( (AChar[0] = #$DB) and (AChar[1] in [#$96..#$9C, #$9F..#$A4,

#$A7..#$A8, #$AA..#$AD]) ) or // Arabic 06D6 (db96).. .. ..06EA (dbaa)
( (AChar[0] = #$E0) and (AChar[1] = #$A3) and (AChar[2] in [#$A4..#$BE]) ) or // Arabic 08E4 (e0a3a4) ..08FE (e0a3be) ( (AChar[0] = #$E1) and (AChar[1] = #$B7) ) or

            // Combining Diacritical Marks Supplement 1DC0-1DFF
    ( (AChar[0] = #$E2) and (AChar[1] = #$83) and (AChar[2] in

[#$90..#$FF]) ) or // Combining Diacritical Marks for Symbols 20D0-20FF
( (AChar[0] = #$EF) and (AChar[1] = #$B8) and (AChar[2] in [#$A0..#$AF]) ) // Combining half Marks FE20-FE2F

   );

end;

It works well. I already created an enumerator for UTF-8 using it. I will do an UTF-16 version soon. First I wanted to add all the possibly missing ranges of combining marks.
It is difficult to find such a list anywhere! How come? The rule is quite simple after all: a codepoint should be combined with a previous codepoint. True or False. Simple.

I did some detective work and studied C# lib sources of Mono project. https://github.com/mono/mono
The string etc. units are extremely complicated. I didn't understand much and didn't find any list of combining marks.
I found something from XmlChar.cs and XmlCharTests.cs which has a function IsCombiningChar. I copy it below.
The problem is that its ranges are completely wrong! For example the first range 0x0346 is also a
combining mark:
http://www.fileformat.info/info/unicode/char/0346/index.htm and so is 0x0347 and 0x0348 etc.
Uhhh...
I guess I could dig out the ranges from here:
http://www.fileformat.info/info/unicode/category/index.htm At least these are combining marks:
[Mc] Mark, Spacing Combining
[Me] Mark, Enclosing
[Mn] Mark, Nonspacing
Are there others?
It will be lots of work because it only shows individual values, not ranges, and there are almost 2000 values!

Any hints anybody?
This feels like reinventing the wheel. There must be a valid updated list of the ranges somewhere.

Juha

---
--- From XmlCharTests.cs ---
---
private static bool IsCombiningChar (int ch)
{
return
(ch >= 0x0300 && ch <= 0x0345) ||
(ch >= 0x0360 && ch <= 0x0361) ||
(ch >= 0x0483 && ch <= 0x0486) ||
(ch >= 0x0591 && ch <= 0x05A1) ||
(ch >= 0x05A3 && ch <= 0x05B9) ||
(ch >= 0x05BB && ch <= 0x05BD) ||
(ch == 0x05BF) ||
(ch >= 0x05C1 && ch <= 0x05C2) ||
(ch == 0x05C4) ||
(ch >= 0x064B && ch <= 0x0652) ||
(ch == 0x0670) ||
(ch >= 0x06D6 && ch <= 0x06DC) ||
(ch >= 0x06DD && ch <= 0x06DF) ||
(ch >= 0x06E0 && ch <= 0x06E4) ||
(ch >= 0x06E7 && ch <= 0x06E8) ||
(ch >= 0x06EA && ch <= 0x06ED) ||
(ch >= 0x0901 && ch <= 0x0903) ||
(ch == 0x093C) ||
(ch >= 0x093E && ch <= 0x094C) ||
(ch == 0x094D) ||
(ch >= 0x0951 && ch <= 0x0954) ||
(ch >= 0x0962 && ch <= 0x0963) ||
(ch >= 0x0981 && ch <= 0x0983) ||
(ch == 0x09BC) ||
(ch == 0x09BE) ||
(ch == 0x09BF) ||
(ch >= 0x09C0 && ch <= 0x09C4) ||
(ch >= 0x09C7 && ch <= 0x09C8) ||
(ch >= 0x09CB && ch <= 0x09CD) ||
(ch == 0x09D7) ||
(ch >= 0x09E2 && ch <= 0x09E3) ||
(ch == 0x0A02) ||
(ch == 0x0A3C) ||
(ch == 0x0A3E) ||
(ch == 0x0A3F) ||
(ch >= 0x0A40 && ch <= 0x0A42) ||
(ch >= 0x0A47 && ch <= 0x0A48) ||
(ch >= 0x0A4B && ch <= 0x0A4D) ||
(ch >= 0x0A70 && ch <= 0x0A71) ||
(ch >= 0x0A81 && ch <= 0x0A83) ||
(ch == 0x0ABC) ||
(ch >= 0x0ABE && ch <= 0x0AC5) ||
(ch >= 0x0AC7 && ch <= 0x0AC9) ||
(ch >= 0x0ACB && ch <= 0x0ACD) ||
(ch >= 0x0B01 && ch <= 0x0B03) ||
(ch == 0x0B3C) ||
(ch >= 0x0B3E && ch <= 0x0B43) ||
(ch >= 0x0B47 && ch <= 0x0B48) ||
(ch >= 0x0B4B && ch <= 0x0B4D) ||
(ch >= 0x0B56 && ch <= 0x0B57) ||
(ch >= 0x0B82 && ch <= 0x0B83) ||
(ch >= 0x0BBE && ch <= 0x0BC2) ||
(ch >= 0x0BC6 && ch <= 0x0BC8) ||
(ch >= 0x0BCA && ch <= 0x0BCD) ||
(ch == 0x0BD7) ||
(ch >= 0x0C01 && ch <= 0x0C03) ||
(ch >= 0x0C3E && ch <= 0x0C44) ||
(ch >= 0x0C46 && ch <= 0x0C48) ||
(ch >= 0x0C4A && ch <= 0x0C4D) ||
(ch >= 0x0C55 && ch <= 0x0C56) ||
(ch >= 0x0C82 && ch <= 0x0C83) ||
(ch >= 0x0CBE && ch <= 0x0CC4) ||
(ch >= 0x0CC6 && ch <= 0x0CC8) ||
(ch >= 0x0CCA && ch <= 0x0CCD) ||
(ch >= 0x0CD5 && ch <= 0x0CD6) ||
(ch >= 0x0D02 && ch <= 0x0D03) ||
(ch >= 0x0D3E && ch <= 0x0D43) ||
(ch >= 0x0D46 && ch <= 0x0D48) ||
(ch >= 0x0D4A && ch <= 0x0D4D) ||
(ch == 0x0D57) ||
(ch == 0x0E31) ||
(ch >= 0x0E34 && ch <= 0x0E3A) ||
(ch >= 0x0E47 && ch <= 0x0E4E) ||
(ch == 0x0EB1) ||
(ch >= 0x0EB4 && ch <= 0x0EB9) ||
(ch >= 0x0EBB && ch <= 0x0EBC) ||
(ch >= 0x0EC8 && ch <= 0x0ECD) ||
(ch >= 0x0F18 && ch <= 0x0F19) ||
(ch == 0x0F35) ||
(ch == 0x0F37) ||
(ch == 0x0F39) ||
(ch == 0x0F3E) ||
(ch == 0x0F3F) ||
(ch >= 0x0F71 && ch <= 0x0F84) ||
(ch >= 0x0F86 && ch <= 0x0F8B) ||
(ch >= 0x0F90 && ch <= 0x0F95) ||
(ch == 0x0F97) ||
(ch >= 0x0F99 && ch <= 0x0FAD) ||
(ch >= 0x0FB1 && ch <= 0x0FB7) ||
(ch == 0x0FB9) ||
(ch >= 0x20D0 && ch <= 0x20DC) ||
(ch == 0x20E1) ||
(ch >= 0x302A && ch <= 0x302F) ||
(ch == 0x3099) ||

        (ch == 0x309A);

}

Reply by email

Juha Manninen
to Martin Frb: Glyph and (de-)composed are different things A glyph may be a ligature…

Here is the new version of my unit dealing with codepoints and Unicode characters. It is now called LazUnicode.
The test program now indeed iterates Unicode characters, meaning that combining diacritical marks are always joined to a previous codepoint. This is the desired behavior _always_. Those marks should never be split apart.

Function UTF16IsCombining is a UTF-16 version of the Martin's function. It works well for my test cases although I still would like to have a comprehensive list of the ranges.

This unit allows to maintain code between Delphi and Lazarus even if you must do some advanced Unicode stuff.
It is quite cool, even if I say it myself!
What more, it produces robust code for UTF-16. There is already enough broken UTF-16 code out there.
Now that problem got solved, too. :)

The project now has 2 build modes, one for each encoding. Switching them is even easier than earlier.
Please test with different Unicode text.

Juha
-------------- next part --------------
A non-text attachment was scrubbed...
Name: LazUnicodeTestPub.tar.gz
Type: application/x-gzip
Size: 4464 bytes
Desc: not available
URL: <http://lists.lazarus-ide.org/pipermail/lazarus/attachments/20160621/f162d4ee/attachment-0001.bin>

Reply by email