URI:
       tCopy old files into repository - flashcards - Stupid LaTeX flashcard viewer
  HTML git clone git://lumidify.org/flashcards.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit d5084133d69805bd727003de6d951ac2de1d8d8d
  HTML 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(-)
       ---
   DIR diff --git a/README b/README
       t@@ -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.
   DIR diff --git a/fm.tex b/fm.tex
       t@@ -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}
   DIR diff --git a/gen_cache.pl b/gen_cache.pl
       t@@ -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;
   DIR diff --git a/render_card.sh b/render_card.sh
       t@@ -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
   DIR diff --git a/viewer.pl b/viewer.pl
       t@@ -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;
   DIR diff --git a/viewer2.pl b/viewer2.pl
       t@@ -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;