commit d5084133d69805bd727003de6d951ac2de1d8d8d
Author: lumidify <nobody@lumidify.org>
Date: Wed, 8 Apr 2020 16:51:18 +0200
Copy old files into repository
Diffstat:
A | README | | | 20 | ++++++++++++++++++++ |
A | fm.tex | | | 31 | +++++++++++++++++++++++++++++++ |
A | gen_cache.pl | | | 23 | +++++++++++++++++++++++ |
A | render_card.sh | | | 22 | ++++++++++++++++++++++ |
A | viewer.pl | | | 137 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | viewer2.pl | | | 144 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 377 insertions(+), 0 deletions(-)
diff --git a/README b/README
@@ -0,0 +1,20 @@
+This is a stupid little flashcard reader for LaTeX files. You probably
+don't want to use it. The images aren't even resized properly. It just
+works for me, that's why I use it.
+
+`viewer.pl` is the old version using Tk, `viewer2.pl` is the new version
+using Gtk2.
+
+The flashcard files are dumped in `latex/`. You then run `./gen_cache.pl`
+to generate the cache. This uses `pdflatex`, `pdfcrop`, and `pdftoppm` to
+convert the LaTeX files to PNG, pasting `fm.tex` at the beginning of the
+flashcard before rendering it.
+
+The top line of each flashcard file has the theorem number or something
+similar, the second line has the front side of the card, and the rest
+of the lines are the actual content of the card.
+
+The actual program only has two buttons, "Next card" and "Reveal card",
+which should be obvious. The number of times the card was viewed is
+saved in `config.json` and a random card from the cards that have been
+viewed the least is displayed each time.
diff --git a/fm.tex b/fm.tex
@@ -0,0 +1,31 @@
+\documentclass[12pt]{article}
+\usepackage[utf8]{inputenc}
+\usepackage[T1]{fontenc}
+\usepackage{amsmath}
+\usepackage{amssymb}
+\usepackage{amsfonts}
+\usepackage{mathrsfs}
+\usepackage{enumitem}
+\usepackage{tikz-cd}
+
+\newcommand{\Bild}{\mathop{\text{Bild}}}
+\newcommand{\chara}{\mathop{\text{char}}}
+\newcommand{\Pol}{\mathop{\text{Pol}}}
+\newcommand{\ggT}{\mathop{\text{ggT}}}
+\newcommand{\Hom}{\mathop{\text{Hom}}}
+\newcommand{\evx}{\mathop{\text{ev}_x}}
+\newcommand{\End}{\mathop{\text{End}}}
+\newcommand{\Alt}{\mathop{\text{Alt}}}
+\newcommand{\sgn}{\mathop{\text{sgn}}}
+\newcommand{\Det}{\mathop{\text{Det}}}
+\newcommand{\Sym}{\mathop{\text{Sym}}}
+\newcommand{\spec}{\mathop{\text{spec}}}
+\newcommand{\Id}{\mathop{\text{Id}}}
+\newcommand{\GL}{\mathop{\text{GL}}}
+\newcommand{\diag}{\mathop{\text{diag}}}
+\newcommand{\K}{\mathop{\mathbb{K}}}
+\newcommand{\N}{\mathop{\mathbb{N}}}
+\newcommand{\sB}{\mathop{\mathscr{B}}}
+\newcommand{\sG}{\mathop{\mathscr{G}}}
+
+\pagestyle{empty}
diff --git a/gen_cache.pl b/gen_cache.pl
@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use open qw< :encoding(UTF-8) >;
+binmode(STDOUT, ":utf8");
+use File::Compare;
+use File::Copy;
+
+if ($#ARGV != 0) {
+ die "USAGE: ./gen_cache.pl <path_to_new_flashcards>\n";
+}
+
+my $path = shift;
+opendir my $dir, $path or die "ERROR: Failed to open flashcard directory!\n";
+while (my $filename = readdir($dir)) {
+ next if ($filename =~ /\A\.\.?\z/);
+ next if (-e "latex/$filename" && compare("latex/$filename", "$path/$filename") == 0);
+ system "./render_card.sh", "$path/$filename";
+ copy "$path/$filename", "latex/$filename";
+}
+closedir $dir;
diff --git a/render_card.sh b/render_card.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+filename="`basename $1`"
+
+meta1=`head -n 1 "$1" | tr -d '\n'`
+meta2=`sed '2!d' "$1" | tr -d '\n'`
+body=`tail -n +3 "$1"`
+
+mkdir tmp
+cd tmp
+
+cat ../fm.tex > tmp.tex
+printf '\\begin{document}\n%s\n\\newpage\n%s\n\\newpage\n%s\n\\end{document}\n' "$meta1" "$meta2" "$body" >> tmp.tex
+pdflatex tmp.tex
+pdfcrop --margins "5 5 5 5" tmp.pdf tmp1.pdf
+pdftoppm -png -rx 200 -ry 200 tmp1.pdf tmp
+mv tmp-1.png "../cache/${filename}_front1.png"
+mv tmp-2.png "../cache/${filename}_front2.png"
+mv tmp-3.png "../cache/${filename}_back.png"
+
+cd ..
+rm -rf tmp
diff --git a/viewer.pl b/viewer.pl
@@ -0,0 +1,137 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use open qw< :encoding(UTF-8) >;
+binmode(STDOUT, ":utf8");
+use Tkx;
+Tkx::package_require('img::png');
+use Data::Dumper;
+use JSON qw(decode_json encode_json);
+use List::Util qw(min);
+
+sub load_cards {
+ my %cards;
+ opendir my $dir, "cache" or die "Unable to open cache directory.\n";
+ while (my $filename = readdir($dir)) {
+ next if ($filename =~ /\A\.\.?\z/);
+ if ($filename =~ /(\A\d\d-\d\d-\d\d_\d\d\D\D)_(.+)\.png\z/) {
+ $cards{$1}{$2} = $filename;
+ }
+ }
+ closedir $dir;
+ return \%cards;
+}
+
+sub load_config {
+ my $path = shift;
+ my $config;
+ if (-e $path) {
+ open my $fh, "<", $path or die "Unable to open config \"$config\".\n";
+ my $json_raw = do {local $/; <$fh>};
+ $config = decode_json $json_raw;
+ close $fh;
+ } else {
+ $config = {};
+ }
+ return $config;
+}
+
+sub remove_cruft {
+ my ($config, $cards) = @_;
+ my $clean_config;
+ my $max_view_count = 0;
+ foreach my $card (keys %$cards) {
+ if (exists $config->{cards}->{$card}) {
+ $clean_config->{cards}->{$card} = $config->{cards}->{$card};
+ } else {
+ $clean_config->{cards}->{$card} = 0;
+ }
+ }
+ return $clean_config;
+}
+
+sub sort_cards {
+ my ($config, $cards) = @_;
+ my $sorted_cards;
+ foreach my $card (keys %$cards) {
+ my $view_count = $config->{cards}->{$card};
+ $sorted_cards->{$view_count}->{$card} = $cards->{$card};
+ }
+ return $sorted_cards;
+}
+
+sub get_rand_card {
+ my $cards = shift;
+ my $min_view_count = min keys(%$cards);
+ my @card_ids = keys %{$cards->{$min_view_count}};
+ my $card = $card_ids[rand @card_ids];
+ return ($min_view_count, $card);
+}
+
+sub next_card {
+ my ($config, $cards, $mw, $frame) = @_;
+
+ $$frame->g_destroy;
+ $$frame = $mw->new_ttk__frame();
+ $$frame->g_grid(-column => 0, -row => 0, -sticky => "nsew");
+
+ my ($view_count, $card_id) = get_rand_card $cards;
+ $mw->g_wm_title($card_id);
+ Tkx::image_create_photo("front1", -file => "cache/${card_id}_front1.png");
+ Tkx::image_create_photo("front2", -file => "cache/${card_id}_front2.png");
+ my $front1 = $$frame->new_ttk__label(-image => "front1");
+ my $front2 = $$frame->new_ttk__label(-image => "front2");
+ $front1->g_grid(-column => 0, -row => 0);
+ $front2->g_grid(-column => 0, -row => 1);
+ return ($view_count, $card_id);
+}
+
+sub gui {
+ my $config = load_config("config.json");
+ my $cards = load_cards;
+ $config = remove_cruft $config, $cards;
+ $cards = sort_cards $config, $cards;
+
+ my $mw = Tkx::widget->new(".");
+ $mw->g_wm_minsize(500,500);
+ my $frame = $mw->new_ttk__frame();
+ $frame->g_grid(-column => 0, -row => 0, -sticky => "nsew");
+ Tkx::grid(rowconfigure => $mw, 0, -weight => 1);
+ Tkx::grid(columnconfigure => $mw, 0, -weight => 1);
+
+ my $view_count;
+ my $card_id;
+
+ # so you can press space bar multiple times without increasing the view count
+ # again (useful for reloading edited cards)
+ my $view_count_increased = 0;
+ $mw->g_bind("<Return>", sub {
+ $view_count_increased = 0;
+ ($view_count, $card_id) = next_card $config, $cards, $mw, \$frame;
+ });
+ $mw->g_bind("<space>", sub {
+ Tkx::image_create_photo("back", -file => "cache/${card_id}_back.png");
+ my $back = $frame->new_ttk__label(-image => "back");
+ $back->g_grid(-column => 0, -row => 2);
+ if (!$view_count_increased) {
+ $view_count_increased = 1;
+ $config->{cards}->{$card_id}++;
+ $cards->{$view_count+1}->{$card_id} = $cards->{$view_count}->{$card_id};
+ delete $cards->{$view_count}->{$card_id};
+ if (!%{$cards->{$view_count}}) {
+ delete $cards->{$view_count};
+ }
+ $frame->new_ttk__label(-text => $view_count + 1)->g_grid(-column => 1, -row => 0);
+ }
+ });
+
+ Tkx::MainLoop;
+ open my $fh, ">", "config.json" or die "Unable to save config.\n";
+ my $json_encoded = encode_json $config;
+ print $fh "$json_encoded\n";
+ close $fh;
+}
+
+gui;
diff --git a/viewer2.pl b/viewer2.pl
@@ -0,0 +1,144 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use utf8;
+use open qw< :encoding(UTF-8) >;
+binmode(STDOUT, ":utf8");
+use Gtk2 '-init';
+use Glib qw/TRUE FALSE/;
+use Data::Dumper;
+use JSON qw(decode_json encode_json);
+use List::Util qw(min);
+
+sub load_cards {
+ my %cards;
+ opendir my $dir, "latex" or die "Unable to open flashcards directory.\n";
+ while (my $filename = readdir($dir)) {
+ next if ($filename =~ /\A\.\.?\z/);
+ $cards{$filename} = 1;
+ }
+ closedir $dir;
+ return \%cards;
+}
+
+sub load_config {
+ my $path = shift;
+ my $config;
+ if (-e $path) {
+ open my $fh, "<", $path or die "Unable to open config \"$config\".\n";
+ my $json_raw = do {local $/; <$fh>};
+ $config = decode_json $json_raw;
+ close $fh;
+ } else {
+ $config = {};
+ }
+ return $config;
+}
+
+sub remove_cruft {
+ my ($config, $cards) = @_;
+ my $clean_config;
+ my $max_view_count = 0;
+ foreach my $card (keys %$cards) {
+ if (exists $config->{cards}->{$card}) {
+ $clean_config->{cards}->{$card} = $config->{cards}->{$card};
+ } else {
+ $clean_config->{cards}->{$card} = 0;
+ }
+ }
+ return $clean_config;
+}
+
+sub sort_cards {
+ my ($config, $cards) = @_;
+ my $sorted_cards;
+ foreach my $card (keys %$cards) {
+ my $view_count = $config->{cards}->{$card};
+ $sorted_cards->{$view_count}->{$card} = $cards->{$card};
+ }
+ return $sorted_cards;
+}
+
+sub get_rand_card {
+ my $cards = shift;
+ my $min_view_count = min keys %$cards;
+ my @card_ids = keys %{$cards->{$min_view_count}};
+ my $card = $card_ids[rand @card_ids];
+ return ($min_view_count, $card);
+}
+
+sub next_card {
+ my ($config, $cards, $img_vbox, $window) = @_;
+ $img_vbox->foreach(sub {$_[0]->destroy});
+ my ($view_count, $card_id) = get_rand_card $cards;
+ $window->set_title($card_id);
+ my $pixbuf1 = Gtk2::Gdk::Pixbuf->new_from_file("cache/${card_id}_front1.png");
+ my $pixbuf2 = Gtk2::Gdk::Pixbuf->new_from_file("cache/${card_id}_front2.png");
+ my $image1 = Gtk2::Image->new_from_pixbuf($pixbuf1);
+ my $image2 = Gtk2::Image->new_from_pixbuf($pixbuf2);
+ $img_vbox->pack_start($image1, FALSE, FALSE, 0);
+ $img_vbox->pack_start($image2, FALSE, FALSE, 0);
+ $img_vbox->show_all;
+ return ($view_count, $card_id);
+}
+
+sub gui {
+ my $config = load_config("config.json");
+ my $cards = load_cards;
+ $config = remove_cruft $config, $cards;
+ $cards = sort_cards $config, $cards;
+
+ my $window = Gtk2::Window->new('toplevel');
+ $window->signal_connect(delete_event => sub {return FALSE});
+ $window->signal_connect(destroy => sub { Gtk2->main_quit; });
+ $window->set_border_width(10);
+
+ my $view_count;
+ my $card_id;
+
+ # so you can press space bar multiple times without increasing the view count
+ # again (useful for reloading edited cards)
+ my $view_count_increased = 0;
+ my $vbox = Gtk2::VBox->new(FALSE, 5);
+ my $hbox = Gtk2::HBox->new(FALSE, 5);
+ my $button = Gtk2::Button->new_with_mnemonic("_Next card");
+
+ my $img_vbox = Gtk2::VBox->new();
+ $button->signal_connect(clicked => sub {
+ $view_count_increased = 0;
+ ($view_count, $card_id) = next_card $config, $cards, $img_vbox, $window;
+ }, $window);
+ $hbox->pack_start($button, FALSE, FALSE, 0);
+ my $label = Gtk2::Label->new("");
+ $button = Gtk2::Button->new_with_mnemonic("_Reveal back");
+ $button->signal_connect(clicked => sub {
+ my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file_at_size("cache/${card_id}_back.png", 1280, 500);
+ my $image = Gtk2::Image->new_from_pixbuf($pixbuf);
+ $img_vbox->pack_start($image, FALSE, FALSE, 0);
+ $image->show;
+ if (!$view_count_increased) {
+ $view_count_increased = 1;
+ $config->{cards}->{$card_id}++;
+ $cards->{$view_count+1}->{$card_id} = $cards->{$view_count}->{$card_id};
+ delete $cards->{$view_count}->{$card_id};
+ if (!%{$cards->{$view_count}}) {
+ delete $cards->{$view_count};
+ }
+ $label->set_text("View count: " . ($view_count + 1));
+ }
+ }, $window);
+ $hbox->pack_start($button, FALSE, FALSE, 0);
+ $hbox->pack_start($label, FALSE, FALSE, 0);
+ $vbox->pack_start($hbox, FALSE, FALSE, 0);
+ $vbox->pack_start($img_vbox, FALSE, FALSE, 0);
+ $window->add($vbox);
+ $window->show_all;
+ Gtk2->main;
+ open my $fh, ">", "config.json" or die "Unable to save config.\n";
+ my $json_encoded = encode_json $config;
+ print $fh "$json_encoded\n";
+ close $fh;
+}
+
+gui;