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 |