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;