diff options
| -rwxr-xr-x | hp41_program_to_pdf.py | 182 | ||||
| -rw-r--r-- | hp41cx-emulator/Makefile | 97 |
2 files changed, 279 insertions, 0 deletions
diff --git a/hp41_program_to_pdf.py b/hp41_program_to_pdf.py new file mode 100755 index 0000000..630a5b6 --- /dev/null +++ b/hp41_program_to_pdf.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +""" +Generate a PDF listing for an HP-41C program with line numbers and a header. + +Requires: reportlab (pip install reportlab) +""" + +from __future__ import annotations + +import argparse +import os +import sys +from datetime import datetime +import re + + +def require_reportlab(): + try: + from reportlab.lib.pagesizes import A4 # noqa: F401 + from reportlab.lib.units import mm # noqa: F401 + from reportlab.pdfgen import canvas # noqa: F401 + except Exception as exc: # pragma: no cover + print("Error: reportlab is required. Install via: pip install reportlab", file=sys.stderr) + raise SystemExit(2) from exc + + +def iter_wrapped_lines(text: str, max_width: float, font_name: str, font_size: int): + from reportlab.pdfbase import pdfmetrics + + if not text: + return [""] + + words = text.split(" ") + lines = [] + current = "" + for word in words: + candidate = word if not current else f"{current} {word}" + width = pdfmetrics.stringWidth(candidate, font_name, font_size) + if width <= max_width: + current = candidate + else: + if current: + lines.append(current) + current = word + if current: + lines.append(current) + return lines + + +def generate_pdf(lines: list[str], output_path: str, program_name: str): + from reportlab.lib.pagesizes import A4 + from reportlab.lib.units import mm + from reportlab.pdfgen import canvas + from reportlab.pdfbase import pdfmetrics + + page_width, page_height = A4 + margin_left = 18 * mm + margin_right = 18 * mm + margin_top = 18 * mm + margin_bottom = 18 * mm + + header_font = "Helvetica-Bold" + body_font = "Courier" + header_size = 14 + body_size = 10 + line_height = 1.35 * body_size + + content_width = page_width - margin_left - margin_right + usable_height = page_height - margin_top - margin_bottom - (header_size + 6) + lines_per_page = int(usable_height // line_height) + if lines_per_page <= 0: + raise ValueError("Page layout too tight. Increase margins or reduce font size.") + + c = canvas.Canvas(output_path, pagesize=A4) + c.setAuthor("hp41_program_to_pdf.py") + c.setTitle(program_name) + + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M") + total_lines = len(lines) + line_number = 1 + page_number = 1 + + def draw_header(): + c.setFont(header_font, header_size) + c.drawString(margin_left, page_height - margin_top, program_name) + c.setFont("Helvetica", 8) + c.drawRightString(page_width - margin_right, page_height - margin_top + 2, timestamp) + c.drawRightString(page_width - margin_right, page_height - margin_top - 10, f"Page {page_number}") + c.line(margin_left, page_height - margin_top - 14, page_width - margin_right, page_height - margin_top - 14) + + y_start = page_height - margin_top - 24 + y = y_start + + draw_header() + c.setFont(body_font, body_size) + + # Pre-parse line numbers when present; keep blank/comment lines but don't + # count them for numbering. + parsed_lines: list[tuple[int | None, str]] = [] + auto_line_no = 1 + number_re = re.compile(r"^\s*(\d+)[\s:]+(.*)$") + for original in lines: + stripped = original.strip() + if stripped == "" or stripped.startswith("#"): + parsed_lines.append((None, original)) + continue + match = number_re.match(original) + if match: + num = int(match.group(1)) + text = match.group(2).rstrip() + parsed_lines.append((num, text)) + auto_line_no = num + 1 + else: + parsed_lines.append((auto_line_no, original)) + auto_line_no += 1 + + numbered = [n for n, _ in parsed_lines if n is not None] + if numbered: + max_digits = max(len(str(n)) for n in numbered) + else: + max_digits = 1 + + line_no_format = f"{{:0{max_digits}d}}" + line_no_width = pdfmetrics.stringWidth("0" * max_digits, body_font, body_size) + 6 + content_width = page_width - margin_left - margin_right - line_no_width + + for line_number, original in parsed_lines: + wrapped = iter_wrapped_lines(original, content_width, body_font, body_size) + for i, wline in enumerate(wrapped): + if y < margin_bottom + line_height: + c.showPage() + page_number += 1 + draw_header() + c.setFont(body_font, body_size) + y = y_start + + if i == 0 and line_number is not None: + c.drawString(margin_left, y, line_no_format.format(line_number)) + c.drawString(margin_left + line_no_width, y, wline) + y -= line_height + + c.save() + if total_lines == 0: + print("Warning: input file had no lines.", file=sys.stderr) + + +def main() -> int: + require_reportlab() + + parser = argparse.ArgumentParser( + description="Generate a PDF listing for an HP-41C program." + ) + parser.add_argument("input", help="Text file with HP-41C program steps, one per line.") + parser.add_argument( + "output", + nargs="?", + help="Output PDF file (default: <input>.pdf).", + ) + parser.add_argument( + "--name", + help="Program name to use in the header (default: input filename).", + ) + args = parser.parse_args() + + input_path = args.input + if not os.path.isfile(input_path): + print(f"Error: input file not found: {input_path}", file=sys.stderr) + return 2 + + output_path = args.output or f"{input_path}.pdf" + program_name = args.name or os.path.splitext(os.path.basename(input_path))[0] + + with open(input_path, "r", encoding="utf-8") as f: + lines = [line.rstrip("\n") for line in f.readlines()] + + generate_pdf(lines, output_path, program_name) + print(f"Wrote {output_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/hp41cx-emulator/Makefile b/hp41cx-emulator/Makefile new file mode 100644 index 0000000..03cea0d --- /dev/null +++ b/hp41cx-emulator/Makefile @@ -0,0 +1,97 @@ +# Makefile for NSIM package +# +# $Id: Makefile 68 2005-04-23 00:34:21Z eric $ +# Copyright 1995, 2003 Eric Smith <eric@brouhaha.com> +# +# 17.08.2014 add Qt Gui files and splitup into +# generic libnsim.a +# nsim the X11 Gui application and +# qtnsim the Qt Gui application +# +# NSIM is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License version 2 as published by the Free +# Software Foundation. Note that I am not granting permission to redistribute +# or modify NSIM under the terms of any later version of the General Public +# License. +# +# This program is distributed in the hope that it will be useful (or at +# least amusing), but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program (in the file "COPYING"); if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +# ----------------------------------------------------------------------------- +# You may need to change the following definitions. In particular you will +# need to remove the -DUSE_TIMER if you don't have the setitimer() system +# call, and you may need to chage X11LIBS and X11INCS if X isn't in /usr/X11. +# ----------------------------------------------------------------------------- +X11LIBS = -L/usr/X11R6/lib -lX11 +X11INCS = -I/usr/X11R6/include +X11SRCS = xio.cpp +X11OBJS = $(addprefix $(OBJDIR)/,$(X11SRCS:.cpp=.o)) + +AR = ar +CC = gcc +CXXFLAGS = -g -O0 -Wall -std=gnu++11 -DWARNING_G $(X11INCS) +#-DUSE_TIMER -DAUTO_POWER_OFF +# ----------------------------------------------------------------------------- +# You shouldn't have to change anything below this point, but if you do please +# let me know why so I can improve this Makefile. +# ----------------------------------------------------------------------------- + +VERSION = 0.62 + +PROGRAMS = nsim qtnsim + +HEADERS = nsim.h hprom.h hplcd.h phineas.h xio.h hprom.h hpram.h ifpf.h +LIBSRCS = nsim.cpp hprom.cpp hplcd.cpp phineas.cpp ifpf.cpp hpram.cpp +MISC = COPYING README NEWS hp41_rom # CHANGELOG + +OBJDIR = obj + +LIBOBJS = $(addprefix $(OBJDIR)/,$(LIBSRCS:.cpp=.o)) + +SOURCES = $(LIBSRCS) $(X11SRCS) +OBJECTS = $(LIBOBJS) $(X11OBJS) +DEPENDS = $(addprefix $(OBJDIR)/,$(SOURCES:.cpp=.d)) + +LIBS = -lstdc++ + +DISTFILES = $(MISC) Makefile $(HEADERS) $(SOURCES) +PACKAGE = nsim +DSTNAME = $(PACKAGE)-$(VERSION) + +all: libnsim.a $(PROGRAMS) + +$(OBJDIR): + -mkdir $@ + +qtnsim: libnsim.a + make -C ./Qtnsim ../$@ + +libnsim.a: $(LIBOBJS) + $(AR) rcs libnsim.a $(LIBOBJS) + +nsim: $(X11OBJS) libnsim.a Makefile + $(CC) -o $@ $(X11OBJS) libnsim.a $(X11LIBS) $(LIBS) + + +$(OBJDIR)/%.o:%.cpp + $(CC) -c $(CXXFLAGS) -MMD -MP -MF"$(@:%.o=%.d)" -o $@ $< + +dist: $(DISTFILES) + -rm -rf $(DSTNAME) + mkdir $(DSTNAME) + for f in $(DISTFILES); do ln $$f $(DSTNAME)/$$f; done + tar --gzip -chf $(DSTNAME).tar.gz $(DSTNAME) + -rm -rf $(DSTNAME) + +clean: + rm -f $(PROGRAMS) $(OBJECTS) $(X11OBJS) $(DEPENDS) libnsim.a + make -C Qtnsim clean + +-include $(DEPENDS)
\ No newline at end of file |
