Add KanjiVG dictionary support

Julien LepillerSun Jun 26 19:21:59+0200 2022

6065526

Add KanjiVG dictionary support

README.md

3030
| ----------- | -------- | ------- | ----------- |
3131
| [JMdict](https://www.edrdg.org/jmdict/edict_doc.html)      | EDRDG    | CC-BY-SA 3.0 | Provides the main search function |
3232
| [RadK](https://www.edrdg.org/krad/kradinf.html) | EDRDG | CC-BY-SA 3.0 | Provides kanji by radical lookup |
33-
| [KanjiDic](https://www.edrdg.org/wiki/index.php/KANJIDIC_Project) | EDRDG | CC-BY-SA 3.0 | Currently used only in combination with RadK |
33+
| [KanjiDic](https://www.edrdg.org/wiki/index.php/KANJIDIC_Project) | EDRDG | CC-BY-SA 3.0 | Provides the main kanji search function |
34+
| [KanjiVG](https://kanjivg.tagaini.net/) | KanjiVG | CC-BY-SA 3.0 | Provides kanji stroke order and elements information |
3435
| [Wadoku](https://wadoku.de) | Wadoku | [Non-commercial license](https://www.wadoku.de/wiki/display/WAD/Wadoku.de-Daten+Lizenz) | Provides the main search function |
3536
| [Jibiki](https://jibiki.fr) | Jibiki | CC-0 | Provides the main search function |
3637

app/src/main/java/eu/lepiller/nani/DictionaryActivity.java

8484
8585
        list_view.setOnItemClickListener((parent, view, position, id) -> {
8686
            Intent intent = new Intent(DictionaryActivity.this, DictionaryDownloadActivity.class);
87-
            intent.putExtra(DictionaryDownloadActivity.EXTRA_DICTIONARY, DictionaryFactory.get(position).getName());
87+
            intent.putExtra(DictionaryDownloadActivity.EXTRA_DICTIONARY, DictionaryFactory.get(position,
88+
                    lang_view.getSelectedItemPosition() == 0? null: lang_view.getSelectedItem().toString()).getName());
8889
8990
            activity.launch(intent);
9091
        });

app/src/main/java/eu/lepiller/nani/dictionary/DictionaryFactory.java

55
import android.os.LocaleList;
66
import android.util.Log;
77
8+
import androidx.annotation.Nullable;
9+
810
import java.io.BufferedReader;
911
import java.io.File;
1012
import java.io.FileNotFoundException;

142144
                                    chooseLanguage(synopsis),
143145
                                    chooseLanguage(description),
144146
                                    cacheDir, url, size, entries, sha256, lang);
147+
                        } else if (type.compareTo("ksvg") == 0) {
148+
                            d = new KanjiVG(name,
149+
                                    chooseLanguage(synopsis),
150+
                                    chooseLanguage(description),
151+
                                    cacheDir, url, size, entries, sha256, lang);
145152
                        }
146153
147154
                        if(d != null) {

284291
        Stack<KanjiResult> results = new Stack<>();
285292
286293
        for(Dictionary d: dictionaries) {
287-
            if (d instanceof KanjiDict && d.isDownloaded()) {
288-
                KanjiResult kanjiResult = ((KanjiDict) d).search(kanji);
294+
            if (d instanceof KanjiDictionary && d.isDownloaded()) {
295+
                KanjiResult kanjiResult = ((KanjiDictionary) d).search(kanji);
289296
                if(kanjiResult != null) {
290297
                    results.add(kanjiResult);
291298
                }

314321
        }
315322
    }
316323
317-
    public static Dictionary get(int position) {
318-
        return dictionaries.get(position);
324+
    public static Dictionary get(int position, @Nullable String lang) {
325+
        if(lang == null || lang.isEmpty())
326+
            return dictionaries.get(position);
327+
328+
        int p = 0;
329+
        for(Dictionary d: dictionaries) {
330+
            if(d.getLang().isEmpty() || d.getLang().equals(lang)) {
331+
                if(p == position)
332+
                    return d;
333+
                p++;
334+
            }
335+
        }
336+
        return null;
319337
    }
320338
321339
    public static ArrayList<Dictionary> getDictionaries(Context context) {

app/src/main/java/eu/lepiller/nani/dictionary/KanjiDict.java

1313
import eu.lepiller.nani.R;
1414
import eu.lepiller.nani.result.KanjiResult;
1515
16-
public class KanjiDict extends FileDictionary {
16+
public class KanjiDict extends KanjiDictionary {
1717
    final private static String TAG = "KANJIDIC";
1818
    private Huffman readingHuffman, meaningHuffman;
1919

5151
        return new KanjiResult(kanji, stroke, meanings, kun, on, nanori, null, null);
5252
    }
5353
54+
    @Override
5455
    KanjiResult search(final String kanji) throws IncompatibleFormatException {
5556
        if (isDownloaded()) {
5657
            try {

app/src/main/java/eu/lepiller/nani/dictionary/KanjiDictionary.java unknown status 1

1+
package eu.lepiller.nani.dictionary;
2+
3+
import java.io.File;
4+
5+
import eu.lepiller.nani.result.KanjiResult;
6+
7+
public abstract class KanjiDictionary extends FileDictionary {
8+
    KanjiDictionary(String name, String description, String fullDescription, File cacheDir, String url, int fileSize, int entries, String hash, String lang) {
9+
        super(name, description, fullDescription, cacheDir, url, fileSize, entries, hash, lang);
10+
    }
11+
12+
    abstract KanjiResult search(final String kanji) throws IncompatibleFormatException;
13+
}

app/src/main/java/eu/lepiller/nani/dictionary/KanjiVG.java unknown status 1

1+
package eu.lepiller.nani.dictionary;
2+
3+
import android.util.Log;
4+
5+
import java.io.File;
6+
import java.io.FileNotFoundException;
7+
import java.io.IOException;
8+
import java.io.RandomAccessFile;
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
13+
import eu.lepiller.nani.R;
14+
import eu.lepiller.nani.result.KanjiResult;
15+
16+
public class KanjiVG extends KanjiDictionary {
17+
    final private static String TAG = "KANJIVG";
18+
    private Huffman commandHuffman;
19+
20+
    KanjiVG(String name, String description, String fullDescription, File cacheDir, String url, int fileSize, int entries, String hash, String lang) {
21+
        super(name, description, fullDescription, cacheDir, url, fileSize, entries, hash, lang);
22+
    }
23+
24+
    @Override
25+
    int getDrawableId() {
26+
        return R.drawable.ic_kanjivg;
27+
    }
28+
29+
    @Override
30+
    public void remove() {
31+
        super.remove();
32+
        commandHuffman = null;
33+
    }
34+
35+
    @Override
36+
    KanjiResult search(String kanji) throws IncompatibleFormatException {
37+
        if(isDownloaded()) {
38+
            try {
39+
                Log.d(TAG, "search for kanji " + kanji);
40+
                RandomAccessFile file = new RandomAccessFile(getFile(), "r");
41+
                byte[] header = new byte[15];
42+
                int l = file.read(header);
43+
                if (l != header.length)
44+
                    return null;
45+
46+
                // Check file format version
47+
                if (!Arrays.equals(header, "NANI_KANJIVG001".getBytes())) {
48+
                    StringBuilder error = new StringBuilder("search: incompatible header: [");
49+
                    boolean first = true;
50+
                    for (byte b : header) {
51+
                        if (first)
52+
                            first = false;
53+
                        else
54+
                            error.append(", ");
55+
                        error.append(b);
56+
                    }
57+
                    error.append("].");
58+
                    Log.d(TAG, error.toString());
59+
                    throw new IncompatibleFormatException(getName());
60+
                }
61+
62+
                Log.d(TAG, "header OK");
63+
64+
                byte[] search = kanji.toLowerCase().getBytes();
65+
                file.skipBytes(4); // size
66+
                commandHuffman = loadHuffman(file);
67+
                long kanjiTriePos = file.getFilePointer();
68+
69+
                Log.d(TAG, "trie pos: " + kanjiTriePos);
70+
71+
                return searchTrie(file, kanjiTriePos, search, new TrieValsDecoder<KanjiResult>() {
72+
                    @Override
73+
                    public KanjiResult decodeVals(RandomAccessFile file1, long pos) throws IOException {
74+
                        Log.d(TAG, "decoding val");
75+
                        file1.seek(pos);
76+
                        return getValue(file1, file1.readInt(), kanji);
77+
                    }
78+
79+
                    @Override
80+
                    public void skipVals(RandomAccessFile file1, long pos) throws IOException {
81+
                        file1.seek(pos);
82+
                        file1.skipBytes(4);
83+
                    }
84+
                });
85+
            } catch (FileNotFoundException e) {
86+
                e.printStackTrace();
87+
            } catch (IOException e) {
88+
                e.printStackTrace();
89+
            }
90+
        }
91+
        return null;
92+
    }
93+
94+
    KanjiResult.Stroke getStroke(RandomAccessFile file) throws IOException {
95+
        String command = getHuffmanString(file, commandHuffman);
96+
        String x = getHuffmanString(file, commandHuffman);
97+
        String y = getHuffmanString(file, commandHuffman);
98+
        return new KanjiResult.Stroke(command, x, y);
99+
    }
100+
101+
    KanjiResult getValue(RandomAccessFile file, long pos, String kanji) throws IOException {
102+
        Log.d(TAG, "getValue at " + pos);
103+
        file.seek(pos);
104+
105+
        List<String> elements = getStringList(file);
106+
        List<KanjiResult.Stroke> strokes = new ArrayList<>();
107+
        int number = file.readShort();
108+
        for(int i=0; i<number; i++) {
109+
            strokes.add(getStroke(file));
110+
        }
111+
112+
        return new KanjiResult(kanji, -1, null, null, null, null, elements, strokes);
113+
    }
114+
}

app/src/main/res/drawable/ic_kanjivg.xml unknown status 1

1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
    android:width="64dp"
3+
    android:height="64dp"
4+
    android:viewportWidth="109"
5+
    android:viewportHeight="109">
6+
7+
  <path
8+
      android:pathData="M0,0h109v109h-109z"
9+
      android:fillColor="#DDDDDD" />
10+
11+
  <path
12+
      android:pathData="M20,19.5c3.62,1.47 8.62,5.38 10.25,8.34"
13+
      android:strokeLineJoin="round"
14+
      android:strokeWidth="3"
15+
      android:fillColor="#00000000"
16+
      android:strokeColor="#bf0909"
17+
      android:strokeLineCap="round"/>
18+
  <path
19+
      android:pathData="M15.25,44.12c4.24,1.43 10.94,5.9 12,8.12"
20+
      android:strokeLineJoin="round"
21+
      android:strokeWidth="3"
22+
      android:fillColor="#00000000"
23+
      android:strokeColor="#bf5e09"
24+
      android:strokeLineCap="round"/>
25+
  <path
26+
      android:pathData="M15.41,89.2c1.46,0.43 2.88,-0.03 3.59,-1.24c2.75,-4.7 5.5,-10.45 8,-16.45"
27+
      android:strokeLineJoin="round"
28+
      android:strokeWidth="3"
29+
      android:fillColor="#00000000"
30+
      android:strokeColor="#bfb009"
31+
      android:strokeLineCap="round"/>
32+
  <path
33+
      android:pathData="M36.07,23.3c2,0.54 5.08,0.53 7.07,0.27C55,21.98 71.25,20.32 83.19,19.6c3.32,-0.2 5.57,0.23 7.24,0.5"
34+
      android:strokeLineJoin="round"
35+
      android:strokeWidth="3"
36+
      android:fillColor="#00000000"
37+
      android:strokeColor="#79bf09"
38+
      android:strokeLineCap="round"/>
39+
  <path
40+
      android:pathData="M49.35,13.24c1.28,0.76 1.96,1.77 2.17,2.86c1.24,6.52 1.74,10.35 2.13,12.65"
41+
      android:strokeLineJoin="round"
42+
      android:strokeWidth="3"
43+
      android:fillColor="#00000000"
44+
      android:strokeColor="#27bf09"
45+
      android:strokeLineCap="round"/>
46+
  <path
47+
      android:pathData="M71.38,9.75c0.62,1.12 1,2.52 0.63,4.18c-1.13,5.19 -1.38,6.69 -2.62,12.91"
48+
      android:strokeLineJoin="round"
49+
      android:strokeWidth="3"
50+
      android:fillColor="#00000000"
51+
      android:strokeColor="#09bf40"
52+
      android:strokeLineCap="round"/>
53+
  <path
54+
      android:pathData="M42,33.71c0.77,0.77 1.54,1.65 1.73,2.54c0.82,3.88 1.52,7.8 2.23,11.75c0.18,1.01 0.36,2.02 0.54,3.02"
55+
      android:strokeLineJoin="round"
56+
      android:strokeWidth="3"
57+
      android:fillColor="#00000000"
58+
      android:strokeColor="#09bf94"
59+
      android:strokeLineCap="round"/>
60+
  <path
61+
      android:pathData="M44.63,35.3c9.5,-1.3 26.87,-3.37 33.86,-4.12c2.88,-0.31 4.83,-0.11 3.87,3.11c-0.97,3.28 -1.83,6.85 -3.21,11.14"
62+
      android:strokeLineJoin="round"
63+
      android:strokeWidth="3"
64+
      android:fillColor="#00000000"
65+
      android:strokeColor="#0997bf"
66+
      android:strokeLineCap="round"/>
67+
  <path
68+
      android:pathData="M47.7,49.58c5.06,-0.4 19.5,-1.97 29.31,-3.03c1.43,-0.15 2.77,-0.3 3.96,-0.43"
69+
      android:strokeLineJoin="round"
70+
      android:strokeWidth="3"
71+
      android:fillColor="#00000000"
72+
      android:strokeColor="#0943bf"
73+
      android:strokeLineCap="round"/>
74+
  <path
75+
      android:pathData="M43.84,59.47c1.39,0.45 3.94,0.58 5.33,0.45c6.04,-0.55 21.02,-2.17 27.94,-2.69c2.31,-0.17 3.71,0.21 4.87,0.44"
76+
      android:strokeLineJoin="round"
77+
      android:strokeWidth="3"
78+
      android:fillColor="#00000000"
79+
      android:strokeColor="#2409bf"
80+
      android:strokeLineCap="round"/>
81+
  <path
82+
      android:pathData="M37.34,72.25c1.62,0.37 4.58,0.45 6.2,0.37c8.01,-0.38 27.71,-2.99 41.25,-3.49c2.69,-0.1 4.31,0.18 5.66,0.36"
83+
      android:strokeLineJoin="round"
84+
      android:strokeWidth="3"
85+
      android:fillColor="#00000000"
86+
      android:strokeColor="#7609bf"
87+
      android:strokeLineCap="round"/>
88+
  <path
89+
      android:pathData="M61.6,34.8c0.48,0.95 0.91,2.34 0.91,4.18c0,44.02 -7.39,48.27 -27.77,57.52"
90+
      android:strokeLineJoin="round"
91+
      android:strokeWidth="3"
92+
      android:fillColor="#00000000"
93+
      android:strokeColor="#bf09b3"
94+
      android:strokeLineCap="round"/>
95+
  <path
96+
      android:pathData="M61,72c8,5.75 18.25,15.12 25.74,19.39c2.42,1.38 4.26,2.24 5.89,2.74"
97+
      android:strokeLineJoin="round"
98+
      android:strokeWidth="3"
99+
      android:fillColor="#00000000"
100+
      android:strokeColor="#bf0961"
101+
      android:strokeLineCap="round"/>
102+
</vector>

app/src/main/res/layout/activity_about.xml

194194
            app:lineHeight="22dp"
195195
            android:text="@string/kanjidic_license" />
196196
197+
        <TextView
198+
            android:layout_width="match_parent"
199+
            android:layout_height="wrap_content"
200+
            android:layout_marginTop="8dp"
201+
            android:lineHeight="22dp"
202+
            android:textSize="@dimen/subtitle_size"
203+
            android:text="@string/kanjivg_title" />
204+
205+
        <TextView
206+
            android:layout_width="match_parent"
207+
            android:layout_height="wrap_content"
208+
            app:lineHeight="22dp"
209+
            android:text="@string/kanjivg_descr" />
210+
211+
        <TextView
212+
            android:layout_width="match_parent"
213+
            android:layout_height="wrap_content"
214+
            app:lineHeight="22dp"
215+
            android:text="@string/kanjivg_license" />
216+
197217
198218
        <TextView
199219
            android:layout_width="match_parent"

app/src/main/res/values/strings.xml

161161
        covers the 13,108 kanji in three main Japanese standards.
162162
    </string>
163163
    <string name="kanjidic_license">This source is licensed under Creative Commons Share-Alike.</string>
164+
    <string name="kanjivg_title">KanjiVG</string>
165+
    <string name="kanjivg_descr">KanjiVG (Kanji Vector Graphics) provides vector graphics and other
166+
        information about kanji used by the Japanese language. For each character, it provides an
167+
        SVG file which gives the shape and direction of its strokes, as well as the stroke order.
168+
        Each file is also enriched with information about the components of the character such as
169+
        the radical, or the type of stroke employed.
170+
    </string>
171+
    <string name="kanjivg_license">This source is licensed under Creative Commons Share-Alike 3.0.</string>
164172
    <string name="wadoku_title">Wadoku.de</string>
165173
    <string name="wadoku_descr">The Wadoku project was created in 2000 as a free German/Japanese
166174
        dictionary. It goal is to create a vocabulary set with a focus on new terms and research