birchill / 10ten-ja-reader

A browser extension to translate Japanese by hovering over words.
https://addons.mozilla.org/firefox/addon/10ten-ja-reader/
GNU General Public License v3.0
564 stars 40 forks source link

Show stroke order of kanji #69

Open sorashi opened 5 years ago

sorashi commented 5 years ago

KanjiVG (GitHub) is a collection of over 10,000 SVG images of kanji with stroke order. There are multiple projects that build upon it, including kanimaji and anikanjivg. These make it possible to animate the stroke order. I think it would be a nice and useful option to show that when looking up kanji.

GIF: 084b8 SVG

birtles commented 5 years ago

Sounds like a good idea. There are a few other higher-priority items I need to work on first, however.

enellis commented 9 months ago

I'd also find it very useful and a nice addition if the stroke order would be displayed in the kanji tab. If not animated, just showing numbers next to the strokes would be sufficient, I think.

SaltfishAmi commented 9 months ago

This can be a very useful thing for learners. But if this is to be added, I would like an option item to disable this feature as well. (Fun fact: Chinese schools stop teaching and testing on stroke orders after around 5th grade and nobody cares about that afterwards)

birtles commented 9 months ago

Thanks for bumping this. Perhaps an interaction where you hover over the character or some area near the character would work.

enellis commented 9 months ago

My suggestion would be, that the numbers will actually be displayed by default. I think, they don't really bother too much, even if you're not interested in the information. Or do they @SaltfishAmi? Then, if you hover over the character, an animation will be played.

SaltfishAmi commented 9 months ago

@Enellis My suggestion would be, that the numbers will actually be displayed by default. I think, they don't really bother too much, even if you're not interested in the information. Or do they @SaltfishAmi? Then, if you hover over the character, an animation will be played.

It's hard to say for now, it depends on the actual appearance of numbers.

And for the numbers, I think we need to consider thoroughly about how to keep a clean view of the character itself. For example, there are some kanjis that are already packed with strokes, like this one:

image

To add stroke order numbers without obscuring the kanji itself seems a complex task. What makes it worse is that most kanjis that I will need to look up are complex ones like this one, because as a Chinese I'm already familiar with all basic kanjis.

enellis commented 9 months ago

I understand why you see a problem with complex kanjis. If we were to use svg files from the KanjiVG set mentioned in the original post, it would look something like this:

In my opinion, the numbers don't obscure the kanji too much, and if you really want to, you can still decipher them. What do you think?

SaltfishAmi commented 9 months ago

Oh. What in the trypophobic nightmare is this. I would definitely not want this. Sorry.

Also, I doubt the readability of the numbers themselves on a non-high-resolution screen.

Tomalak commented 9 months ago

Kanji stroke order could show up on click/tap on the Kanji itself, or on the "x strokes" label, leaving the regular look in place by default.

That would be a neat compromise, since even if you're a learner who's interested in stroke order, most of the time you only need to see it when you're unsure in one spot and need a reminder, or for a really rare outlier Kanji where stroke order cannot be inferred.

enellis commented 9 months ago

Yeah, I see how some people really wouldn't like this kind of messy display by default. The click/tap action on the Kanji is occupied for copying, though. Clicking or hovering over the strokes label is a good suggestion I think.

sorashi commented 9 months ago

fwiw I imagined something like this. The animation could be triggered by a mouse click.

cut.webm

enellis commented 4 weeks ago

Aside from the visual design decisions, how would we want to integrate the KanjiVG dataset into this project? The folder containing the seperate SVG files is approximately 53MB in size (or ~22MB when zipped). The three options coming to my mind are (from what I feel is worst to best but I'm really not sure):

What are your thoughts on these approaches, or do you have any other suggestions for integrating the dataset efficiently?

birtles commented 3 weeks ago

@Enellis Thanks for raising this! Animating SVGs is a topic dear to my heart (I've spent most of my career working on it!) so I'm looking forward to integrating this.

The timing is also very good since we just shipped 1.19 which, thanks to @StarScape, includes the first steps towards rebuilding the popup code using Preact which will make adding interactivity to the kanji tab a lot easier.

UX thoughts

Regarding the UX, I think we've mostly converged on the following features:

The tricky part is that clicking the kanji currently brings up the "Copy to clipboard" overlay so we would need to either:

  1. Introduce a new way of triggering the copy action (e.g. adding a "Copy" button below the character) and then making clicking/tapping the character trigger the stroke animation.
  2. Introduce a different way of triggering the stroke animation (e.g. on desktop browsers hovering over the kanji might work but for mobile browsers we might need to make the stroke count section clickable or add a "Play" button).

Handling the KanjiVG data

Regarding the KanjiVG data set I had a quick look now. Taking the very first file there, 0f9a8.svg for 令 it looks like we could reduce the data quite a lot. Currently it looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2009/2010/2011 Ulrich Apel.
This work is distributed under the conditions of the Creative Commons
Attribution-Share Alike 3.0 Licence. This means you are free:
* to Share - to copy, distribute and transmit the work
* to Remix - to adapt the work

Under the following conditions:
* Attribution. You must attribute the work by stating your use of KanjiVG in
  your own copyright header and linking to KanjiVG's website
  (http://kanjivg.tagaini.net)
* Share Alike. If you alter, transform, or build upon this work, you may
  distribute the resulting work only under the same or similar license to this
  one.

See http://creativecommons.org/licenses/by-sa/3.0/ for more details.
-->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [
<!ATTLIST g
xmlns:kvg CDATA #FIXED "http://kanjivg.tagaini.net"
kvg:element CDATA #IMPLIED
kvg:variant CDATA #IMPLIED
kvg:partial CDATA #IMPLIED
kvg:original CDATA #IMPLIED
kvg:part CDATA #IMPLIED
kvg:number CDATA #IMPLIED
kvg:tradForm CDATA #IMPLIED
kvg:radicalForm CDATA #IMPLIED
kvg:position CDATA #IMPLIED
kvg:radical CDATA #IMPLIED
kvg:phon CDATA #IMPLIED >
<!ATTLIST path
xmlns:kvg CDATA #FIXED "http://kanjivg.tagaini.net"
kvg:type CDATA #IMPLIED >
]>
<svg xmlns="http://www.w3.org/2000/svg" width="109" height="109" viewBox="0 0 109 109">
<g id="kvg:StrokePaths_0f9a8" style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;">
<g id="kvg:0f9a8" kvg:element="令">
    <g id="kvg:0f9a8-g1" kvg:element="人" kvg:position="top" kvg:radical="general">
        <path id="kvg:0f9a8-s1" kvg:type="㇒" d="M49.62,13.25c0.11,0.94,0.38,2.48-0.22,3.77c-4.15,8.86-15.15,25.23-36.65,37.08"/>
        <path id="kvg:0f9a8-s2" kvg:type="㇏" d="M50.54,16.55c6.13,4.35,24.99,20.22,33.98,27.33c3.22,2.54,5.6,4.12,9.73,5.37"/>
    </g>
    <g id="kvg:0f9a8-g2" kvg:position="bottom">
        <g id="kvg:0f9a8-g3" kvg:element="一">
            <path id="kvg:0f9a8-s3" kvg:type="㇐" d="M 38.590625,42.910833 c 1.76,0.72 3.84,0.36 5.65,0.14 5.4,-0.66 13.08,-1.76 18.48,-2.24 1.88,-0.17 3.54,-0.23 5.37,0.21"/>
        </g>
        <g id="kvg:0f9a8-g4" kvg:element="卩" kvg:original="マ">
            <path id="kvg:0f9a8-s4" kvg:type="㇆" d="M 31.464375,53.641042 c 0.61,0.15 3,1 4.21,0.87 10.329583,-0.937708 28.549375,-2.998125 38.130833,-4.17 1.516086,-0.185427 4.278829,-0.290121 3.95,2.89 -0.431171,4.169879 -2.680149,16.919928 -6,23.84 -1.890149,3.939928 -3.18,3.45 -6.23,0.46"/>
            <path id="kvg:0f9a8-s5" kvg:type="㇑" d="M 44.769166,53.809375 c 0.87,0.87 1.8,2 1.8,3.5 0,7.36 -0.04,24.53 -0.1,34.13 -0.02,3.3 -0.05,5.71 -0.08,6.51"/>
        </g>
    </g>
</g>
</g>
<g id="kvg:StrokeNumbers_0f9a8" style="font-size:8;fill:#808080">
    <text transform="matrix(1 0 0 1 42.50 15.13)">1</text>
    <text transform="matrix(1 0 0 1 58.50 19.63)">2</text>
    <text transform="matrix(1 0 0 1 43 40)">3</text>
    <text transform="matrix(1 0 0 1 25.1 62.1)">4</text>
    <text transform="matrix(1 0 0 1 36.4 65.5)">5</text>
</g>
</svg>

The draw me a kanji source appears to only use the id field (and even then, it's not clear that's really necessary) so we could probably reduce this down to just:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109 109">
  <g id="kvg:StrokePaths_0f9a8" style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;">
    <g id="kvg:0f9a8">
      <g id="kvg:0f9a8-g1">
        <path id="kvg:0f9a8-s1" d="M49.62,13.25c0.11,0.94,0.38,2.48-0.22,3.77c-4.15,8.86-15.15,25.23-36.65,37.08"/>
        <path id="kvg:0f9a8-s2" d="M50.54,16.55c6.13,4.35,24.99,20.22,33.98,27.33c3.22,2.54,5.6,4.12,9.73,5.37"/>
      </g>
      <g id="kvg:0f9a8-g2">
        <g id="kvg:0f9a8-g3">
          <path id="kvg:0f9a8-s3" d="M 38.590625,42.910833 c 1.76,0.72 3.84,0.36 5.65,0.14 5.4,-0.66 13.08,-1.76 18.48,-2.24 1.88,-0.17 3.54,-0.23 5.37,0.21"/>
        </g>
        <g id="kvg:0f9a8-g4">
          <path id="kvg:0f9a8-s4" d="M 31.464375,53.641042 c 0.61,0.15 3,1 4.21,0.87 10.329583,-0.937708 28.549375,-2.998125 38.130833,-4.17 1.516086,-0.185427 4.278829,-0.290121 3.95,2.89 -0.431171,4.169879 -2.680149,16.919928 -6,23.84 -1.890149,3.939928 -3.18,3.45 -6.23,0.46"/>
          <path id="kvg:0f9a8-s5" d="M 44.769166,53.809375 c 0.87,0.87 1.8,2 1.8,3.5 0,7.36 -0.04,24.53 -0.1,34.13 -0.02,3.3 -0.05,5.71 -0.08,6.51"/>
        </g>
      </g>
    </g>
  </g>
</svg>

We don't need to repeat the styling information in each file and if it turns out we don't need the grouping or the path IDs we could reduce this down to just:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109 109">
<path d="M49.62,13.25c0.11,0.94,0.38,2.48-0.22,3.77c-4.15,8.86-15.15,25.23-36.65,37.08"/>
<path d="M50.54,16.55c6.13,4.35,24.99,20.22,33.98,27.33c3.22,2.54,5.6,4.12,9.73,5.37"/>
<path d="M 38.590625,42.910833 c 1.76,0.72 3.84,0.36 5.65,0.14 5.4,-0.66 13.08,-1.76 18.48,-2.24 1.88,-0.17 3.54,-0.23 5.37,0.21"/>
<path d="M 31.464375,53.641042 c 0.61,0.15 3,1 4.21,0.87 10.329583,-0.937708 28.549375,-2.998125 38.130833,-4.17 1.516086,-0.185427 4.278829,-0.290121 3.95,2.89 -0.431171,4.169879 -2.680149,16.919928 -6,23.84 -1.890149,3.939928 -3.18,3.45 -6.23,0.46"/>
<path d="M 44.769166,53.809375 c 0.87,0.87 1.8,2 1.8,3.5 0,7.36 -0.04,24.53 -0.1,34.13 -0.02,3.3 -0.05,5.71 -0.08,6.51"/>
</svg>

That works out at 749 bytes as opposed 3,031 bytes for the original which is a reduction of about 75%.

We could reduce the precision of the path data using SVGO and get that down to 588 bytes for a total reduction of over 80%.

Assuming the other characters can be reduced similarly, we could get the total uncompressed storage from 50Mb to 10~12Mb or so. Obviously the XPI file packaging will further compress that to something more manageable.

I think those numbers line up pretty closely with the XML file you mentioned. Where is that XML file, by the way?

Bundling the data

We already bundle a 30Mb data file for the flat file dictionary, so I think bundling an extra 14Mb data file (or 14Mb of SVG files) for stroke order is probably reasonable. Users only download this data when the add-on is updated which is only every month or so and, once compressed, it's only the size of a large image anyway.

So I think we can rule out your third option of loading off the network in the background. That leaves the other two options which I understand being:

  1. Package the individual SVG files into the add-on, expose them via web_accessible_resources, have the content script perform a fetch() to get the appropriate file and then parse out its path data and animate it.
  2. Load all the data into the background script via the large XML file then have the content script send requests to the background script for the path data of a particular character.

Like you mentioned, (2) has the disadvantage of increasing the add-on's memory footprint, at least for the background script. It's also probably more code overall and more states to manage. As a result, I think (1) is worth a try.

I'd prefer to write our own animation code rather than incorporating another library to avoid cruft and because I think we can do better. For example, draw me a kanji introduces a dependency on Raphael and kanjivganimate relies on setTimeout which is known to be problematic for synchronizing animations.

Given that we are going to be parsing out the path data anyway, it might not even be necessary to store the characters as SVG files. They could possibly even just be JSON files with an array of SVG path data strings.

enellis commented 3 weeks ago

@birtles Thank you for your detailed response. I truly appreciate your dedication to this project and want to thank you for the continuous effort you put into it, especially considering that you do this for free.

UX

KanjiVG

The XML file I referred to is generated with each full release of the KanjiVG repository by a python script and can be found in the assets. For example, the entry for 令 would look like this:

<kanji id="kvg:kanji_0f9a8">
<g id="kvg:0f9a8" kvg:element="令">
    <g id="kvg:0f9a8-g1" kvg:element="人" kvg:position="top" kvg:radical="general">
        <path id="kvg:0f9a8-s1" kvg:type="㇒" d="M49.62,13.25c0.11,0.94,0.38,2.48-0.22,3.77c-4.15,8.86-15.15,25.23-36.65,37.08"/>
        <path id="kvg:0f9a8-s2" kvg:type="㇏" d="M50.54,16.55c6.13,4.35,24.99,20.22,33.98,27.33c3.22,2.54,5.6,4.12,9.73,5.37"/>
    </g>
    <g id="kvg:0f9a8-g2" kvg:position="bottom">
        <g id="kvg:0f9a8-g3" kvg:element="一">
            <path id="kvg:0f9a8-s3" kvg:type="㇐" d="M 38.590625,42.910833 c 1.76,0.72 3.84,0.36 5.65,0.14 5.4,-0.66 13.08,-1.76 18.48,-2.24 1.88,-0.17 3.54,-0.23 5.37,0.21"/>
        </g>
        <g id="kvg:0f9a8-g4" kvg:element="卩" kvg:original="マ">
            <path id="kvg:0f9a8-s4" kvg:type="㇆" d="M 31.464375,53.641042 c 0.61,0.15 3,1 4.21,0.87 10.329583,-0.937708 28.549375,-2.998125 38.130833,-4.17 1.516086,-0.185427 4.278829,-0.290121 3.95,2.89 -0.431171,4.169879 -2.680149,16.919928 -6,23.84 -1.890149,3.939928 -3.18,3.45 -6.23,0.46"/>
            <path id="kvg:0f9a8-s5" kvg:type="㇑" d="M 44.769166,53.809375 c 0.87,0.87 1.8,2 1.8,3.5 0,7.36 -0.04,24.53 -0.1,34.13 -0.02,3.3 -0.05,5.71 -0.08,6.51"/>
        </g>
    </g>
</g>
</kanji>

A little bit more verbose compared to your proposed data reduction, but of course as you said, we could write our own tool to extract the data we need in the format that suits us best.

birtles commented 3 weeks ago

@Enellis, likewise thank you for your very helpful input here!

UX

  • I personally really like the idea of introducing a play button in the stroke count section. This seems like an intuitive way to trigger the animation without conflicting with the "Copy to clipboard" function and would look kind of neat.

Great! I think once we've converted the kanji tab to Preact and have a React Cosmos fixture for it, it will be easier to prototype a play button.

  • Regarding the decision to not show stroke numbers at all, I have this scenario in my mind: if someone is using the extension to learn how to write kanji and is uncertain about the order of the last few strokes of a complex kanji, they would have to watch the entire animation to see the correct sequence. Having the option to display stroke numbers at a glance can provide immediate clarification and might enhance the learning experience.

I can see that. I still think it will be hard to present the numbers in a clear and attractive manner. What if, after the user presses "Play" to show the animation, we showed a scrubber underneath the animation that lets you skip back and forth in the animation so you can jump to the part you're interested in?

KanjiVG

The XML file I referred to is generated with each full release of the KanjiVG repository by a python script and can be found in the assets.

Great, thank you!

I thought about this a bit more and I think another option might be just to incorporate the stroke data into our kanji database.

We already have a mechanism for downloading the kanji data and updating it. The kanji data itself is the combination of a number of data sources (KANJIDIC, 漢検 data, education level data, WaniKani data, custom kanji component data, Conning reference data, etc.) that we merge together as part of a data pipeline that runs every day. If we merge the path data in as part of that job then we can re-use all our existing code for updating the data and looking it up. It would simply be another field attached to the kanji data that we pass from background script to content script.

The main downside is that whole data pipeline is something I maintain on AWS and is not really in a format amenable to contributing at the moment so it would basically have to wait for me to integrate the data upstream before we could use it.

enellis commented 3 weeks ago

What if, after the user presses "Play" to show the animation, we showed a scrubber underneath the animation that lets you skip back and forth in the animation so you can jump to the part you're interested in?

Good idea, a scrubber would be a nice solution for this. Maybe we can add little markers to the scrubber to show the beginning of a new stroke. I we kept the radical data, we could even associate each stroke with its radical and color it accordingly for an even better overview. I have something like this in mind: slider

The main downside is that whole data pipeline is something I maintain on AWS and is not really in a format amenable to contributing at the moment so it would basically have to wait for me to integrate the data upstream before we could use it.

If integrating the stroke data into the existing pipeline is the most efficient and maintainable solution, I'm sure we all will be patiently and happily waiting. Let us know, if there is something we can help you with :)

birtles commented 3 weeks ago

Good idea, a scrubber would be a nice solution for this. Maybe we can add little markers to the scrubber to show the beginning of a new stroke. I we kept the radical data, we could even associate each stroke with its radical and color it accordingly for an even better overview. I have something like this in mind: slider

The markers are a good idea. I wonder about the aesthetics of coloring the strokes, however. I was imagining something along the lines of @sorashi's mockup in https://github.com/birchill/10ten-ja-reader/issues/69#issuecomment-1742697476 so that the final character is all one color.

However, I noticed you used shades of green. That's an interesting idea. Maybe if we used shades of the same color it wouldn't look so bad.

Alternatively (or as well as) we could probably render little thumbnail strokes inside the markers but they might be too small to be useful.

The main downside is that whole data pipeline is something I maintain on AWS and is not really in a format amenable to contributing at the moment so it would basically have to wait for me to integrate the data upstream before we could use it.

If integrating the stroke data into the existing pipeline is the most efficient and maintainable solution, I'm sure we all will be patiently and happily waiting. Let us know, if there is something we can help you with :)

Thanks! I'll get to it!

enellis commented 3 weeks ago

The markers are a good idea. I wonder about the aesthetics of coloring the strokes, however. I was imagining something along the lines of @sorashi's mockup in #69 (comment) so that the final character is all one color.

I don't even think that coloring the Kanji itself is necessary. Just coloring the markers inside the scrubber should be sufficient because, when learning to write Kanji, one quickly gets a sense of which radicals a Kanji is composed of and the general rules of thumb, like left-to-right, top-to-bottom, horizontal first, etc. By simply looking at the colored scrubber, users can already get an overview of where the part of the animation that interests them is located.

Alternatively (or as well as) we could probably render little thumbnail strokes inside the markers but they might be too small to be useful.

Good idea. Definitely worth a try, I think.

birtles commented 3 weeks ago

I don't even think that coloring the Kanji itself is necessary.

That's good to hear. Was there any particular meaning behind the shades of green?

enellis commented 3 weeks ago

Not really, I just thought that, like you already mentioned, shades of one color would look better and more streamlined than using completely different colors. The choice of color was kind of arbitrary.