Add KanjiVG dictionary support
README.md
30 | 30 | | ----------- | -------- | ------- | ----------- | | |
31 | 31 | | [JMdict](https://www.edrdg.org/jmdict/edict_doc.html) | EDRDG | CC-BY-SA 3.0 | Provides the main search function | | |
32 | 32 | | [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 | | |
34 | 35 | | [Wadoku](https://wadoku.de) | Wadoku | [Non-commercial license](https://www.wadoku.de/wiki/display/WAD/Wadoku.de-Daten+Lizenz) | Provides the main search function | | |
35 | 36 | | [Jibiki](https://jibiki.fr) | Jibiki | CC-0 | Provides the main search function | | |
36 | 37 |
app/src/main/java/eu/lepiller/nani/DictionaryActivity.java
84 | 84 | ||
85 | 85 | list_view.setOnItemClickListener((parent, view, position, id) -> { | |
86 | 86 | 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()); | |
88 | 89 | ||
89 | 90 | activity.launch(intent); | |
90 | 91 | }); |
app/src/main/java/eu/lepiller/nani/dictionary/DictionaryFactory.java
5 | 5 | import android.os.LocaleList; | |
6 | 6 | import android.util.Log; | |
7 | 7 | ||
8 | + | import androidx.annotation.Nullable; | |
9 | + | ||
8 | 10 | import java.io.BufferedReader; | |
9 | 11 | import java.io.File; | |
10 | 12 | import java.io.FileNotFoundException; | |
… | |||
142 | 144 | chooseLanguage(synopsis), | |
143 | 145 | chooseLanguage(description), | |
144 | 146 | 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); | |
145 | 152 | } | |
146 | 153 | ||
147 | 154 | if(d != null) { | |
… | |||
284 | 291 | Stack<KanjiResult> results = new Stack<>(); | |
285 | 292 | ||
286 | 293 | 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); | |
289 | 296 | if(kanjiResult != null) { | |
290 | 297 | results.add(kanjiResult); | |
291 | 298 | } | |
… | |||
314 | 321 | } | |
315 | 322 | } | |
316 | 323 | ||
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; | |
319 | 337 | } | |
320 | 338 | ||
321 | 339 | public static ArrayList<Dictionary> getDictionaries(Context context) { |
app/src/main/java/eu/lepiller/nani/dictionary/KanjiDict.java
13 | 13 | import eu.lepiller.nani.R; | |
14 | 14 | import eu.lepiller.nani.result.KanjiResult; | |
15 | 15 | ||
16 | - | public class KanjiDict extends FileDictionary { | |
16 | + | public class KanjiDict extends KanjiDictionary { | |
17 | 17 | final private static String TAG = "KANJIDIC"; | |
18 | 18 | private Huffman readingHuffman, meaningHuffman; | |
19 | 19 | ||
… | |||
51 | 51 | return new KanjiResult(kanji, stroke, meanings, kun, on, nanori, null, null); | |
52 | 52 | } | |
53 | 53 | ||
54 | + | @Override | |
54 | 55 | KanjiResult search(final String kanji) throws IncompatibleFormatException { | |
55 | 56 | if (isDownloaded()) { | |
56 | 57 | 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
194 | 194 | app:lineHeight="22dp" | |
195 | 195 | android:text="@string/kanjidic_license" /> | |
196 | 196 | ||
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 | + | ||
197 | 217 | ||
198 | 218 | <TextView | |
199 | 219 | android:layout_width="match_parent" |
app/src/main/res/values/strings.xml
161 | 161 | covers the 13,108 kanji in three main Japanese standards. | |
162 | 162 | </string> | |
163 | 163 | <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> | |
164 | 172 | <string name="wadoku_title">Wadoku.de</string> | |
165 | 173 | <string name="wadoku_descr">The Wadoku project was created in 2000 as a free German/Japanese | |
166 | 174 | dictionary. It goal is to create a vocabulary set with a focus on new terms and research |