flashcards

Stupid LaTeX flashcard viewer
git clone git://lumidify.org/flashcards.git
Log | Files | Refs | README

commit d5084133d69805bd727003de6d951ac2de1d8d8d
Author: lumidify <nobody@lumidify.org>
Date:   Wed,  8 Apr 2020 16:51:18 +0200

Copy old files into repository

Diffstat:
AREADME | 20++++++++++++++++++++
Afm.tex | 31+++++++++++++++++++++++++++++++
Agen_cache.pl | 23+++++++++++++++++++++++
Arender_card.sh | 22++++++++++++++++++++++
Aviewer.pl | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aviewer2.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;