Back

Tools for \LaTeX Typesettings

Python GUI to help insert images and PDF documents in LaTeX\LaTeX documents.

Quickly insert images

The following code uses PySimpleGUI

import os
import PySimpleGUI as sg
import tkinter as tk  # Add this line to import the tk module

# Define the command-line arguments
import argparse

import ctypes
import platform


def make_dpi_aware():
    if int(platform.release()) >= 8:
        ctypes.windll.shcore.SetProcessDpiAwareness(True)

# 为了正确处理含空格的路径
class MyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, ' '.join(values))


parser = argparse.ArgumentParser(description="Generate LaTeX commands for inserting figures.")
parser.add_argument("--callPath", nargs='+', help="the path of the calling file", action=MyAction)
args = parser.parse_args()

if args.callPath:
    dir = os.path.abspath(args.callPath)
    dis = True
else:
    dir = "C:/Users/LiuGe/Documents"
    dis = False

# 设置全局选项
sg.set_options(font=('Arial', 11))

# Create the layout for the window
layout = [
    [sg.Frame("文件选择",[
        [sg.Text("选择一个图像文件:")],
        [sg.Input(key="-FILE-"), sg.FileBrowse(key="fileSelector",initial_folder=dir)],
        [sg.Text("-callPath 参数"), sg.FolderBrowse(key="callPath", initial_folder="C:/Users/LiuGe/Documents", disabled=args.callPath is not None)],
    ])],
    [sg.Frame("路径相关设置",[
        [sg.Checkbox("使用相对路径", key="-USE RELATIVE PATH-")],
        [sg.Text("相对路径: -", key="-RELATIVE PATH TEXT-")],
    ])],
    [sg.Frame("布局设置",[
        [sg.Text("浮动体位置:"),sg.Combo(["H", "T", "P"], default_value="H", key="-FLOAT-")],
        [sg.Checkbox("包含 center 环境", key="-CENTER-",default=True)],
        [sg.Text("图像宽度 (0-1):"),sg.Slider(range=(0, 1), default_value=0.7, resolution=0.01, orientation="h", key="-WIDTH-")],
        [sg.Checkbox("Only Contain Inclusion", key="-GRAPHICS-", default=False)],
    ])],
    [sg.Frame("标题部分",[
        [sg.Checkbox("添加图像标签", key="-CAPTION-")],
        [sg.Checkbox("不编号的图像标签", key="-UNNUMBERED CAPTION-")],
        [sg.InputText(key="-LABEL-", disabled=False)],
    ])],
    [sg.Frame("CMD Part",[
        [sg.Button("生成命令"), sg.Button("复制到剪贴板"), sg.Button("退出")],
        [sg.Multiline(key="-LATEX CMDS-", size=(80, 10))]
    ])]
]

# Create the window with the layout, size=(800, 600)
window = sg.Window("插入图像", layout)

# Wait for the window to be closed
while True:
    make_dpi_aware()
    event, values = window.read()
    if event == sg.WIN_CLOSED or event == "退出":
        break

    if args.callPath or values["callPath"]:
        if args.callPath:
            window["fileSelector"].update(os.path.abspath(args.callPath))
        else:
            window["fileSelector"].update(os.path.abspath(values["callPath"]))


    # Render the LaTeX command
    if values["-FILE-"] != "":
        # Get the values of the input fields
        align = values["-FLOAT-"]
        include_center = values["-CENTER-"]
        width = values["-WIDTH-"]
        image_path = values["-FILE-"]
        captionTF = values["-CAPTION-"]
        unnumbered_caption = values["-UNNUMBERED CAPTION-"]
        caption = values["-LABEL-"]

        # Check if the "use relative path" checkbox is checked
        if values["-USE RELATIVE PATH-"]:
            # Get the relative path to the image file
            if args.callPath or values["callPath"]:
                if args.callPath:
                    # If the callPath argument is used, use the path of the calling file
                    call_dir = os.path.abspath(args.callPath)

                else:
                    # If the callPath argument is not used, use the path of the selected directory
                    call_dir = os.path.abspath(values["callPath"])
                # # Get the directory of the calling file
                # call_dir = os.path.dirname(os.path.abspath(args.callPath))
                # Get the relative path to the image file
                relative_path = os.path.relpath(image_path, call_dir).replace("\\", "/")
            else:
                # If there is no callPath argument, use the absolute path of the image file
                relative_path = "-"
                # window["-USE RELATIVE PATH-"].update(disabled=True)
            image_path = relative_path
            window["-RELATIVE PATH TEXT-"].update(f"相对路径: {relative_path}")
        else:
            window["-RELATIVE PATH TEXT-"].update("相对路径: -")

        # Render the LaTeX command using a formatted string
        if values["-GRAPHICS-"]:
            result = f"\\includegraphics[width={width}\\textwidth]{{{image_path}}}"
        else:
            result = f"\\begin{{figure}}[{align}]"
            if include_center:
                result += "\n\\begin{center}"
            result += f"\n\\includegraphics[width={width}\\textwidth]{{{image_path}}}"
            if include_center:
                result += "\n\\end{center}"
            if captionTF or unnumbered_caption:
                if unnumbered_caption:
                    result += f"\n\\caption*{{{caption}}}"
                else:
                    result += f"\n\\caption{{{caption}}}"
            result += "\n\\end{figure}"


        # Update the output field with the rendered LaTeX command
        window["-LATEX CMDS-"].update(result)

    # Copy the generated commands to the clipboard
    if event == "复制到剪贴板":
        root = tk.Tk()
        root.withdraw()
        root.clipboard_clear()
        root.clipboard_append(values["-LATEX CMDS-"])
        root.update()
        root.destroy()
        #sg.popup("已复制到剪贴板!")
        sg.popup_quick_message("已复制到剪贴板!", title=None, background_color=None, text_color=None, auto_close_duration=500)


# Close the window
window.close()

Quickly insert PDF files

import argparse
# import click
import os
import PySimpleGUI as sg
from PyPDF2 import PdfReader
import pyperclip

# Click CLI-Arg
# @click.command()
# @click.option('--mainPath',help='CallPath')

import ctypes
import platform


def make_dpi_aware():
    if int(platform.release()) >= 8:
        ctypes.windll.shcore.SetProcessDpiAwareness(True)

def get_pdf_page_count(file_path):
    try:
        with open(file_path, 'rb') as file:
            pdf_reader = PdfReader(file)
            page_count = len(pdf_reader.pages)
        return page_count
    except Exception as e:
        sg.popup(f"无法打开文件: {file_path}\n错误信息: {e}")
        return None


def parse_page_range(page_range, page_count):
    try:
        if page_range:
            # If the user has input a page range, parse it into a list of page numbers
            pages = []
            for part in page_range.split(","):
                if "-" in part:
                    # If the part is a sub-range, add the pages in the sub-range to the list
                    start, end = map(int, part.split("-") if part else [])
                    if start <= 0 or end > page_count or start >= end:
                        # If the sub-range is invalid, show an error message and reset the page range
                        sg.popup("无效的页码范围")
                        page_range = f"1-{page_count}"
                        break
                    pages.extend(range(start, end+1))
                else:
                    # If the part is a single page number, add it to the list
                    page = int(part)
                    if page <= 0 or page > page_count:
                        # If the page number is invalid, show an error message and reset the page range
                        sg.popup("无效的页码")
                        page_range = f"1-{page_count}"
                        break
                    pages.append(page)
            else:
                # If all parts of the page range are valid, join them into a comma-separated string
                page_range = ",".join(map(str, pages))
        else:
            # If the user has not input a page range, use all pages in the PDF file
            pages = list(range(1, page_count+1))
            page_range = f"1-{page_count}"

        # Group the page numbers into contiguous ranges
        page_ranges = []
        current_range = []
        for page in sorted(pages):
            if not current_range or page == current_range[-1] + 1:
                current_range.append(page)
            else:
                page_ranges.append(current_range)
                current_range = [page]
        if current_range:
            page_ranges.append(current_range)

        # Generate a string representation of the page ranges
        page_range_text = ",".join(
            [f"{r[0]}-{r[-1]}" if len(r) > 1 else str(r[0]) for r in page_ranges])

        return page_range, page_range_text
    except Exception as e:
        sg.popup(f"无法解析页码范围: {page_range}\n错误信息: {e}")
        return None, None


def get_page_ranges(pages):
    """Convert a list of page numbers to a list of page ranges."""
    ranges = []
    start = pages[0]
    end = pages[0]
    for page in pages[1:]:
        if page == end + 1:
            end = page
        else:
            if start == end:
                ranges.append(str(start))
            else:
                ranges.append(f"{start}-{end}")
            start = page
            end = page
    if start == end:
        ranges.append(str(start))
    else:
        ranges.append(f"{start}-{end}")
    return ranges


def parse_page_range2(page_range, page_count):
    """Parse a page range string and return a list of page numbers."""
    if not page_range:
        return list(range(1, page_count+1)), "全部"
    pages = []
    parts = page_range.split(",")
    for part in parts:
        if "-" in part:
            start, end = part.split("-")
            start = int(start.strip())
            end = int(end.strip())
            pages.extend(range(start, end+1))
        else:
            pages.append(int(part.strip()))
    pages = sorted(set(pages))
    page_range_text = ",".join(get_page_ranges(pages))
    return pages, page_range_text



# 为了正确处理含空格的路径
class MyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, ' '.join(values))

# Parse command-line arguments
parser = argparse.ArgumentParser()
parser.add_argument("-callPath", nargs='+' ,help="Directory of the calling application", action=MyAction)
args = parser.parse_args()

# 设置全局选项
sg.set_options(font=('msyh.ttc', 11))
# 检查args.path是否非空
if args.callPath:
    default_path = os.path.abspath(args.callPath)
else:
    default_path = "C:/Users/LiuGe/Documents"

# Define the layout of the GUI
layout = [
    [sg.Frame("选择文件", [
        [sg.Text("文件选择"), sg.InputText(), sg.FileBrowse(key="file1",initial_folder=default_path)]
    ])],
    [sg.Frame("命令行选项", [
        [sg.Text("-callPath 参数"), sg.FolderBrowse(key="callPath", initial_folder="C:/Users/LiuGe/Documents/Mac-Windows-NUC",disabled=args.callPath is not None)]
    ])],
    [sg.Frame("PDF 选项", [
        [sg.Checkbox("Fit Paper", key="fit_paper")],
        [sg.Text("Scale:"), sg.Slider(range=(0.1, 1.0), default_value=1.0,
                                      resolution=0.1, orientation="h", size=(20, 15), key="scale_slider")],
        [sg.Text("页码范围 (例如: 1-5, 7, 9-10)"), sg.InputText(key="page_range")]
    ])],
    [sg.Frame("路径选项", [
        [sg.Text("相对路径: -", key="relative_path_text")],
        [sg.Checkbox("使用相对路径", key="use_relative_path_button")]
    ])],
    [sg.Frame("命令选项", [
        [sg.Submit("生成 LaTeX 命令"), sg.Button("生成分离的 LaTeX 命令"), sg.Cancel()],
        [sg.Text("页数信息: -", key="page_count_text")],
        [sg.Multiline("LaTeX Commands: -", size=(80, 20),
                      key="latex_commands_text")],
        [sg.Button("复制 LaTeX 命令到剪贴板", key="copy_latex_commands_button")]
    ])]
]


# Create the GUI window
window = sg.Window("插入PDF文件", layout)

while True:
    # 根据DPI更改清晰度
    make_dpi_aware()
    # Wait for an event to occur
    event, values = window.read()

    # If the window is closed or the Cancel button is clicked, exit the loop
    if event == sg.WINDOW_CLOSED or event == "Cancel":
        break

    # Persistent Scale
    window['scale_slider'].update(sg.user_settings_get_entry('-Scale-'))

    # Get the path of the selected PDF file
    pdf_file_path = values["file1"]

    # Get the number of pages in the PDF file
    page_count = get_pdf_page_count(pdf_file_path)
    page_count_text = f"页数信息: {page_count} 页"
    window["page_count_text"].update(page_count_text)

    # Determine whether the "Fit Paper" option should be included in the LaTeX command
    fitpaper_option = "fitpaper" if values["fit_paper"] else ""

    sg.user_settings_set_entry('-Scale-', values['scale_slider'])

    # Parse the user's input for the page range
    page_range = values["page_range"]
    page_range, page_range_text = parse_page_range(page_range, page_count)
    # Get the scale value from the slider
    scale = values["scale_slider"]

    # If the 生成 LaTeX 命令 button is clicked, generate the LaTeX command
    if event == "生成 LaTeX 命令":

        # Include the "Fit Paper" option and page range in the LaTeX command
        comma = "," if fitpaper_option else ""
        latex_cmd = f"\\includepdf[offset=0mm 0mm, pages={{{page_range_text}}}{comma}{fitpaper_option}, scale={scale}, pagecommand={{}}]{{{pdf_file_path}}} "
        window["latex_commands_text"].update(latex_cmd)

        if args.callPath or values["callPath"]:
            if args.callPath:
                call_path = os.path.abspath(args.callPath)
                window["file1"].update(call_path)
            else:
                call_path = os.path.abspath(values["callPath"])
                window["file1"].update(call_path)


        # Get the relative path of the selected file in terms of the callPath argument
        relative_path_text = ""
        if args.callPath or values["callPath"]:
            if args.callPath:
                call_path = os.path.abspath(args.callPath)
            else:
                call_path = os.path.abspath(values["callPath"])
            if os.path.splitdrive(pdf_file_path)[0] != os.path.splitdrive(call_path)[0]:
                # If the selected file is on a different drive than the callPath argument,
                # use the absolute path of the selected file instead of the relative path
                relative_path_text = f"相对路径: -"
            else:
                relative_path = os.path.relpath(
                    pdf_file_path, call_path).replace("\\", "/")
                relative_path_text = f"相对路径: {relative_path}"
        # else:
            # window["use_relative_path_button"].update(disabled=True)
        window["relative_path_text"].update(relative_path_text)

        if values["use_relative_path_button"]:
            latex_cmd = latex_cmd.replace(pdf_file_path, relative_path)
            window["latex_commands_text"].update(latex_cmd)
            window["relative_path_text"].update(f"相对路径: {relative_path}")

        # Print the LaTeX command and the relative path of the selected file
        print(latex_cmd)
        print(relative_path_text)

    if event == "生成分离的 LaTeX 命令":
        # Parse the page range and generate the separate LaTeX commands
        page_range = values["page_range"]
        pages, page_range_text = parse_page_range2(page_range, page_count)
        separate_cmds = []
        separate_cmds = []
        comma = "," if fitpaper_option else ""
        for page in pages:
            separate_cmd = f"\\includepdf[offset=0mm 0mm, pages={{{page}}}{comma}{fitpaper_option}, scale={scale}, pagecommand={{}}]{{{pdf_file_path}}} \n"
            separate_cmds.append(separate_cmd)
        separate_cmds_text = "\n".join(separate_cmds)
        window["latex_commands_text"].update(separate_cmds_text)

        # Get the relative path of the selected file in terms of the callPath argument
        relative_path_text = ""
        if args.callPath:
            call_path = os.path.abspath(args.callPath)
            if os.path.splitdrive(pdf_file_path)[0] != os.path.splitdrive(call_path)[0]:
                # If the selected file is on a different drive than the callPath argument,
                # use the absolute path of the selected file instead of the relative path
                relative_path_text = f"相对路径: -"
            else:
                relative_path = os.path.relpath(
                    pdf_file_path, call_path).replace("\\", "/")
                relative_path_text = f"相对路径: {relative_path}"
        else:
            window["use_relative_path_button"].update(disabled=True)
        window["relative_path_text"].update(relative_path_text)

        if values["use_relative_path_button"]:
            separate_cmds_text = separate_cmds_text.replace(
                pdf_file_path, relative_path)
            window["latex_commands_text"].update(separate_cmds_text)
            window["relative_path_text"].update(f"相对路径: {relative_path}")

        print(separate_cmds_text)
        print(relative_path_text)

    # If the 复制 LaTeX 命令到剪贴板 button is clicked, copy the LaTeX command to the clipboard
    if event == "copy_latex_commands_button":
        pyperclip.copy(window["latex_commands_text"].get())
        sg.popup(f"LaTeX 命令已复制到剪贴板。\n {window['latex_commands_text'].get()}")



# Close the GUI window
window.close()