본문 바로가기
아래아한글 자동화/python+hwp 중급

[QnA] "PDF가 모아찍기로 출력돼요" #PDF 출력시 파이썬으로 모아찍기 일괄해제

by 일코 2022. 9. 21.
파이썬으로 특정 폴더에 있는 한글(hwp)파일들을 PDF로 전환하려고하는데..
한글에서 마지막 저장 시 두쪽 모아찍기로 인쇄하고 저장을 하면 해당 설정을 기억해서 파이썬으로 불러들여서 PDF로 저장하면 한페이지에 두장 모아 찍기로 PDF가 저장이 됩니다.
저장된 파일의 최종 인쇄설정을 무시하고 종이 한장에 1페이지 인쇄 설정한 상태로 전환하여 PDF로 저장하고 싶은데.. 쉽지 않네요

파이썬으로 여러 개의 HWP 파일을 PDF로 변환하는 건 사실 굉장히 간단한 작업입니다. 코드도 짧죠.

예를 들어, 위와 같이 hwp파일이 여러 개 있고, 동일한 파일명에 확장자만 바꿔서 PDF파일을 생성하려면?

아래와 같이 코드를 짜볼 수 있겠죠.

import os
import win32com.client as win32

os.chdir(r"C:\Users\smj02\Desktop\새 폴더")  # 콘솔 위치를 한/글파일들이 있는 곳으로 이동
hwp_list = os.listdir()  # 한/글 파일명 리스트 생성

hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")  # 한/글 열기

for file in hwp_list:
    hwp.Open(os.path.join(os.getcwd(), file))  # 각각의 한/글 파일을 하나씩 열고
    hwp.XHwpDocuments.Item(0).XHwpPrint.filename = os.path.join(os.getcwd(), file.replace('.hwp', '.pdf'))  # 저장할 pdf파일명을 지정해준 후 
    hwp.XHwpDocuments.Item(0).XHwpPrint.RunToPDF()  # PDF 파일 생성한 다음
    hwp.Run("FileClose")  # 해당 한/글 파일 닫아주기

hwp.Quit()  # 한/글 프로그램 종료

 

다소 간단한 코드지만 아래처럼 잘 실행되는 것을 볼 수 있습니다.

그런데 예상치 못한 문제가 한 가지 발생합니다.

ㅁㄴㅇㄹ - 복사본 (2).hwp 파일은 원래 이렇게 생겼는데

 

변환된 PDF파일을 열어보니, 2쪽 모아찍기가 적용되어 있습니다?

ㅁㄴㅇㄹ - 복사본 (2).pdf

어라? 동일한 내용의 ㅁㄴㅇㄹ.PDF 파일은 1쪽씩 잘 변환되어 있는데요?

ㅁㄴㅇㄹ.pdf

질문자님이 지금 딱 이 상황인 겁니다.

수백(?) 개의 한/글 파일을 PDF로 변환하고 있는데 몇 개의 파일들만 수작업 때 모아찍기 설정 후에 저장한 상태라서

위의 예시와 같이 모아찍기로 출력이 되어버린 겁니다.

시간 아깝게 이걸 언제 하나씩 일일이 열어보면서 확인하고, 변환하고, 저장하고, 다시 출력할까요ㅜㅜㅜ

이 때 파이썬으로 모아찍기를 일괄해제할 수 있을까요?

예. 가능합니다. 

대신 위에서 사용한 단축명령어로는 모아찍기 해제 코드가 먹지 않으므로(버그인 듯?)

아래처럼 코드를 바꾸면 됩니다.

아래 코드는 kosohn님께서 댓글로 알려주셨습니다.
다시 한 번 감사드립니다.
import os
import win32com.client as win32

os.chdir(r"C:\Users\smj02\Desktop\새 폴더")  # 콘솔 위치를 한/글파일들이 있는 곳으로 이동
hwp_list = os.listdir()  # 한/글 파일명 리스트 생성

hwp = win32.gencache.EnsureDispatch("hwpframe.hwpobject")  # 한/글 열기


def pdf인쇄(pdf_filename, printername="Hancom PDF", printmethod=0):
    "thanx to kosohn"
    act = hwp.CreateAction("Print")
    pset = act.CreateSet()
    act.GetDefault(pset)
    pset.SetItem("PrintMethod", printmethod)  # 0:보통출력, 4:2쪽모아찍기 등
    pset.SetItem("FileName", pdf_filename)  # 전체경로 포함한 파일이름
    pset.SetItem("PrinterName", printername)  # "Microsoft Print to PDF" 등
    act.Execute(pset)
    
    
for file in hwp_list:
    hwp.Open(os.path.join(os.getcwd(), file))  # 각각의 한/글 파일을 하나씩 열고
    filename = os.path.join(os.getcwd(), file.replace('.hwp', '.pdf'))  # 저장할 pdf파일명을 지정해준 후 
    pdf인쇄(filename)
    hwp.Run("FileClose")  # 해당 한/글 파일 닫아주기

hwp.Quit()  # 한/글 프로그램 종료

이상입니다. 넘 간단하네요^^

다시 한 번 테스트를 해보고 마치겠습니다.

다시 한 번 복사본 (2), (3), (4) 파일을 모아찍기로 설정 및 저장한 후에

 파이썬으로 각각 파일을 열고 PDF변환을 시작합니다.

모아찍기로 설정되어 있던 한/글 파일도 모두 기본인쇄로 변경되어 PDF변환이 완료되었습니다.

끝으로, 한 가지 기억해 두셔야 할 것은

(중요한 점은 아니지만) 이렇게 모아찍기를 해제하고 출력을 하더라도,

한/글 파일을 열어보면 여전히 "2쪽모아찍기"로 저장되어 있다는 점입니다.

질문자 님의 문제도 이렇게 해결하시기를 바랍니다.

 


 

아래부터는 xml편집을 통한 변경방법입니다.

삽질 히스토리이므로ㅜ 무시 바랍니다.

제 대답은 "...물론 가능하기는 하다" 입니다. 

굳이 pyautoguiwin32 모듈을 써서 마우스나 키보드를 조작하는 (제가 좋아하지 않는) RPA 방식이 아니라도

가능한데, 다만 특정 메서드가 만들어져 있는 게 아니라서 구현이 조금 복잡합니다...

(제가 지식이 짧아서 더 멋진 방법을 아직 못 찾았습니다.)

 

복잡한 방법을 먼저 말씀드리면,

① hwpx로 포맷변환 후 압축을 풀고

② settings.xml을 열어서 PrintMethod 값을 4(2쪽모아찍기)에서 0(보통출력)으로 고치고

③ 다시 zip 압축하고 hwpx 또는 hwp로 확장자를 고치면 됩니다...

이건 사실 뭐.. 배보다 배꼽이 큰 느낌이네요. 파일 해킹하는 줄ㄷㄷ

사실 아주 간단한 방법도 있기는 합니다.

3.0~97 버전의 hwp나, 개방형 표준문서(owpml) 또는 hwpml 등의 포맷으로 변환(hwp.SaveAs)한 후

변환된 파일을 다시 열면, 모아찍기 설정값이 초기화되기는 합니다.

변환하는 코드는 매크로녹화로 아주 간단히 얻을 수 있겠고요.

(다만, 포맷변환으로 인해 일부 서식이 깨질 리스크가 있겠습니다.)

 

한/글 파일은 어디서 어떻게 깨질지 모르기 때문에

리스크가 가장 적어 보이는 "복잡한 방법"을 코드와 주석으로 설명드리겠습니다.

 

코드는 아래와 같습니다. 주석을 많이 남겼습니다.

def 모아찍기냐():
    """
    모든 hwp파일을 조건없이 hwpx로 변환하면 비효율적이므로 
    필터링을 위해 모아찍기 상태로 저장된 문서인지 체크하는 함수를 만들어봄.
    모아찍기인지 확인할 수 있는 PrintMethod 프로퍼티는 좀 특이한데
    실제 hwp파일은 "2쪽 모아찍기"를 나타내는 "4"로 저장된 상태라도 
    PrintMethod를 호출해보면 "보통 출력"인 "0"으로 나타남.
    인쇄를 하거나(중간에 취소해도 됨), PDF로 변환을 시도하거나, 미리보기를 한 번 해야만
    실제 값인 "4"로 바뀜. (이걸 동기식이라고 부름?)
    그래서 temp라는 이름으로 PDF 출력을 먼저 한 번 했고, 귀찮아서 삭제는 못함ㅜ
    """
    hwp.XHwpDocuments.Item(0).XHwpPrint.filename = "temp"
    hwp.XHwpDocuments.Item(0).XHwpPrint.RunToPDF()
    if hwp.XHwpDocuments.Item(0).XHwpPrint.PrintMethod != 0:
        print(f"모아찍기 파일 발견 : {os.path.basename(hwp.XHwpDocuments.Item(0).FullName)}")
        return True
    return False
    

def 모아찍기해제():
    """
    기존의 hwp파일이 열려 있는 상태에서 일련의 절차를 거쳐
    모아찍기를 해제하는 함수.
    요약하자면 압축을 해제하고 내부의 xml파일의 설정값을 바꾼 후
    다시 zip포맷으로 압축해서 hwp로 확장자를 바꿈.
    간혹 뜨는 액세스 오류나 FileExist 오류를 피하기 위해
    하위폴더를 만들었다 변환했다 지웠다 하느라 코드가 더 길어짐ㅜ
    (변수명도 살짝 꼬여 있지만, 이해는 여러분의 몫으로 남겨둠..ㅜㅜㅜ)
    그래도 잘 작동함.
    """
    hwpx_file = hwp.XHwpDocuments.Item(0).FullName  # 현재 열린 파일이름과 경로전체
    hwp.SaveAs(hwpx_file + "x", Format="HWPX")  # hwpx로 변환
    hwp.Run("FileClose")  # 열려 있던 문서를 닫아주고
    os.chdir(os.path.dirname(hwpx_file))  # 콘솔위치를 현재 문서가 있는 디렉토리로 옮긴 후
    path = os.path.join(os.getcwd(), "hwpx")  # "hwpx"라는 하위폴더를 생성하기 위해 path 정의
    with zipfile.ZipFile(hwpx_file + "x", 'r') as zf:
        zf.extractall(path=path)  # 압축해제
    os.chdir(path)  # 하위폴더인 "hwpx"로 이동
    tree = ET.parse(os.path.join(os.getcwd(), "settings.xml"))  # 해당 설정파일 파싱 시작
    root = tree.getroot()  # 최상위 태그 찾은 뒤
    for child in root.iter():  # 하위 태그를 순회하면서
        if child.get("name") == "PrintMethod":  # 모아찍기 정보가 들어있는 구간을 찾아서
            child.text = "0"  # 값을 "보통출력"으로 설정하고
    ET.ElementTree(root).write(os.path.join(path, "settings.xml"))  # 저장
    shutil.make_archive("../temp", "zip", ".")  # (shutil로 폴더압축할 수 있는 건 이제야 알았네요ㅎ)
    os.chdir("..")  # 다시 원래 폴더로 돌아와서
    shutil.rmtree(path)  # 압축이 풀려 있었던 하위폴더인 "hwpx"를 삭제하고
    os.remove(hwpx_file)  # 최초에 열려 있었던 기존 hwp파일은 삭제함
    os.remove(hwpx_file + "x")  # hwpx 포맷으로 변환했던 파일도 삭제함.
    os.rename("temp.zip", hwpx_file)  # 대신 temp.zip파일의 이름을 기존의 hwp파일명으로 변경
    hwp.Open(hwpx_file)  # 최종생성된 hwp(hwpx)파일을 한/글에서 열기

모아찍기상태인지 확인하는 함수와, 모아찍기를 보통출력으로 변환하는 두 개의 함수를 만들어보았습니다.

그리고 전체 코드는 아래와 같습니다.

필요하신 분 계시면 활용하시라고 하드코딩을 줄이고 간단한 GUI도 tkinter로 붙여놓았습니다.
import sys
import shutil
import xml.etree.ElementTree as ET
import zipfile
import os
from tkinter import Tk, messagebox
from tkinter.filedialog import askopenfilenames
import win32com.client as win32


def 한글시작(visible=False):
    hwp = win32.gencache.EnsureDispatch('HWPFrame.HwpObject')
    hwp.RegisterModule('FilePathCheckDLL', 'FilePathCheckerModule')
    hwp.XHwpWindows.Item(0).Visible = visible
    return hwp


def 파일목록선택():
    """
    병합할 한/글 파일을 모두 선택하는 함수.
    파일명을 포함한 한/글 파일의 전체경로가 리스트로 저장됨
    """
    root = Tk()  # GUI 실행하고
    filenames = askopenfilenames(title="PDF로 변환할 한/글 파일을 선택해주세요.",
                                 initialdir=os.getcwd(),
                                 filetypes=[("한/글파일", "*.hwp *.hwpx")])
    root.destroy()  # GUI 종료
    return filenames


def 모아찍기냐():
    hwp.XHwpDocuments.Item(0).XHwpPrint.filename = "temp"
    hwp.XHwpDocuments.Item(0).XHwpPrint.RunToPDF()
    if hwp.XHwpDocuments.Item(0).XHwpPrint.PrintMethod != 0:
        print(f"모아찍기 파일 발견 : {os.path.basename(hwp.XHwpDocuments.Item(0).FullName)}")
        return True
    return False


def 모아찍기해제():
    print("기본인쇄로 변환합니다.\n")
    hwpx_file = hwp.XHwpDocuments.Item(0).FullName
    hwp.SaveAs(hwpx_file + "x", Format="HWPX")
    hwp.Run("FileClose")
    os.chdir(os.path.dirname(hwpx_file))
    path = os.path.join(os.getcwd(), "hwpx")
    with zipfile.ZipFile(hwpx_file + "x", 'r') as zf:
        zf.extractall(path=path)
    os.chdir(path)
    tree = ET.parse(os.path.join(os.getcwd(), "settings.xml"))
    root = tree.getroot()
    for child in root.iter():
        if child.get("name") == "PrintMethod":
            child.text = "0"
    ET.ElementTree(root).write(os.path.join(path, "settings.xml"))
    shutil.make_archive("../temp", "zip", ".")
    os.chdir("..")
    shutil.rmtree(path)
    os.remove(hwpx_file)
    os.remove(hwpx_file + "x")
    os.rename("temp.zip", hwpx_file)
    hwp.Open(hwpx_file)


def pdf저장(file):
    hwp.XHwpDocuments.Item(0).XHwpPrint.filename = os.path.join(BASE_DIR, file.replace('.hwp', '.pdf'))
    hwp.XHwpDocuments.Item(0).XHwpPrint.RunToPDF()
    hwp.Run("FileClose")


def 한글종료(hwp):
    hwp.XHwpDocuments.Close(isDirty=False)
    hwp.Quit()  # 한/글 종료


if __name__ == '__main__':
    filelist = 파일목록선택()
    try:
        BASE_DIR = os.path.dirname(filelist[0])
    except IndexError:
        messagebox.showwarning("선택한 파일 없음", "파일을 선택하지 않아\n프로그램이 종료됩니다.")
        sys.exit()

    hwp = 한글시작(visible=True)
    for file in filelist:  # 현재폴더 안에 있는 모든 파일을
        hwp.Open(os.path.join(BASE_DIR, file))  # 한/글로 열어서
        if 모아찍기냐():
            모아찍기해제()
        pdf저장(file)
    한글종료(hwp)

한 번 테스트해볼까요?

현재 복사본(2), 복사본(3), 복사본(4) 등 세 개의 파일을 모아찍기 상태로 저장해 두었습니다.

원하는 인쇄방식 선택 후, "인쇄" 또는 "미리보기" 버튼 클릭 후 취소하고 저장하면 됩니다.

코드를 돌려보겠습니다.

의도대로 모아찍기가 전부 해제된 것을 확인할 수 있었습니다.

 

마치며

쉽다면 쉽지만 어렵다면 어려운 내용의 포스팅이었던 것 같습니다.

이런 포스팅 때문에 한/글 자동화에 혹여 부담을 느끼시거나 겁을 먹지 않으셨으면 좋겠습니다.

여러분이 활용하시게 될 한/글 보고서 자동화 작업의 약 90%는 정말정말 단순한 필드삽입 노가다일 거라고 확신합니다.

실제로 제가 가장 자주 받는 많은 질문도 누름틀과 필드 관련이고요..

그 외에 루틴하지만 시간이 너무 많이 걸리는 작업인데 파이썬으로 자동화하기 어렵다?

그러면 방명록에 사연 적어주시거나, 메일(martinii.fun@지메일)로 보내주세요.

저는 이미 밑천이 다 동난 상태라서 더 포스팅할 내용도 없습니다.

심지어 코딩이나 컴퓨터와는 아무 상관도 없는 업에 종사하고 있어서, 아이디어 짜낼 시간 내기도 귀찮습니다.

그러니, 질문 보내 주시면 감사한 마음으로 답변 작성 드리겠습니다.

그럼 이번 포스팅은 여기서 마칩니다.

행복한 하루 되세요!!

앞으로도 도움이 되는 콘텐츠를 만들어 가겠습니다.

 

댓글