pretty code

2026年5月3日 星期日

Swift to Python

差點忘了這件事,上個星期六晚上無聊,請 Claude Code 直接幫我 porting 一個我在電子書討論區看到的小工具。

由於該工具是用 Swift 寫的,本人雖然使用過很多程式語言,但我不喜歡蘋果,故我也不會想去學蘋果體系的東西。

所以我只是簡單下個指示。

There is a tool in the folder of original_tool. It is wriiten by Swift.
Please rewrite the tool by Python.
Please use git to record your porting.
Please put all codes in python_tool folder.

一開始還以為 token 會爆掉,沒想到也是不到 20 分鐘就 porting 完畢。



當然從圖片看不出 bug,目前 porting 的版本有兩個 bug:

1. render 區會一直閃爍,我猜程式寫得不好,變成一直 loop 更新。
2. 雖說可以區分哪些字體無法用在直式閱讀,但直式顯示畫面標點符號並沒有變成直式格式。
我猜是報告中提到的限制?

下面是請 AI 整理的報告

================================================================
  DEVELOPMENT REPORT: Chinese Vertical Font Viewer (Python Port)
================================================================
Date     : 2026-04-25
Author   : tylpk  (assisted by Claude Sonnet 4.6)
Project  : c_font_tool
Repo     : C:\Users\tylpk\Desktop\AI\c_font_tool


----------------------------------------------------------------
1. PROJECT OVERVIEW
----------------------------------------------------------------

Goal
  Port the macOS Swift app "Chinese-Vertical-font-reading-test"
  (found in original_tool/) to Python so it can run on Windows
  and other platforms without Xcode or macOS.

Original tool
  - Language  : Swift 5 / SwiftUI
  - Platform  : macOS 13.0+, built with Xcode 15+
  - Source    : original_tool/Chinese-Vertical-font-reading-test/
  - Key files :
      ChineseVerticalApp.swift   -- app entry point
      ContentView.swift          -- main UI, font loading, EPUB handling,
                                    font analysis logic
      VerticalTextView.swift     -- CoreText-based vertical text rendering
      FontDetailView.swift       -- details panel sub-view

Python tool output
  - Language  : Python 3.10+
  - Platform  : Windows / macOS / Linux (anywhere Python + tkinter run)
  - Source    : python_tool/
  - Entry     : python_tool/main.py


----------------------------------------------------------------
2. FILE STRUCTURE
----------------------------------------------------------------

python_tool/
  main.py              (  23 lines)  Entry point; launches TkinterDnD
                                     or plain tkinter root window.
  app.py               ( 522 lines)  Main GUI application class.
  font_analyzer.py     ( 321 lines)  Font metadata extraction and
                                     vertical-support analysis.
  vertical_renderer.py ( 124 lines)  Pillow-based vertical text renderer.
  epub_reader.py       ( 132 lines)  EPUB / TXT text extraction.
  requirements.txt     (   3 lines)  Python package dependencies.

Total Python source: 1,122 lines


----------------------------------------------------------------
3. DEPENDENCIES
----------------------------------------------------------------

Package          Version  Purpose
---------------  -------  --------------------------------------
fonttools        >=4.40   Parse font binary tables (cmap, name,
                          GSUB, OS/2) for metadata and glyph
                          coverage analysis.
Pillow           >=10.0   Render TrueType/OpenType glyphs onto
                          an in-memory image for the vertical
                          text display canvas.
tkinterdnd2      >=0.3    Enable drag-and-drop of font/book files
                          onto the tkinter window. Gracefully
                          absent — file dialogs work as fallback.

All three were already installed or installed successfully via:
  pip install -r python_tool/requirements.txt


----------------------------------------------------------------
4. FEATURE MAPPING (Swift → Python)
----------------------------------------------------------------

4.1 Font Loading
  Swift  : CTFontManagerCreateFontDescriptorsFromURL + CTFontCreate
  Python : fonttools TTFont / TTCollection
  Notes  : TTC collections are fully supported; all sub-fonts are
           exposed as separate entries (Font Name [0], [1], …).

4.2 Font Metadata (details panel)
  Fields ported:
    Full Name, Family, Chinese Name, Chinese Family,
    PostScript Name, Style, File Size, File Path,
    Glyph Count, Ascent / Descent,
    CJK Punctuation (24 chars), Vertical Forms (20 forms),
    Rotation correctness summary.

  Chinese name extraction
    Swift  : Parse raw 'name' table bytes manually.
    Python : fonttools NameRecord.toUnicode(); filter by
             Windows platform Chinese language IDs
             (0x0804 zh-CN, 0x0404 zh-TW, 0x0C04 zh-HK, …)
             and Mac platform IDs (33 zh-Hans, 19 zh-Hant).

4.3 CJK Punctuation Coverage
  Checks 24 standard CJK punctuation code points against the
  font's cmap.  Missing glyphs are listed by character and
  flagged as warnings.  Their code points are passed to the
  renderer which replaces them with □ drawn in red.

4.4 Vertical Forms Coverage (U+FE10–FE44)
  Checks 20 vertical presentation form code points against cmap.
  Reports: ✓ complete / △ incomplete / ✗ none.

4.5 Rotation Check (CLREQ compliance)
  The most technically involved analysis:

  Swift  : Shape each character in both a horizontal and a
           vertical CTFrame, compare resulting glyph IDs via
           CoreText.  A changed glyph ID means GSUB 'vert'
           substituted it.
  Python : Traverse the GSUB table with fonttools; collect all
           glyph-name → glyph-name mappings from lookups
           referenced by 'vert' and 'vrt2' features.
           Compare base glyph name to the substitution target.

  Rules applied (matching Swift logic exactly):
    shouldRotate = True  (brackets, em-dash, ellipsis):
      GSUB substitutes base → correct
      base cmap == vert-form cmap (already vertical) → correct
      no substitution, not already vertical → WRONG (highlighted)
    shouldRotate = False (fullwidth colon, semicolon):
      GSUB substitutes base → WRONG (font incorrectly rotates)
      base cmap == vert-form cmap → WRONG (glyph is vertical form)
      no substitution → correct

  Checked characters (20 total):
    :;()《》「」『』〈〉【】〔〕〖〗—…

4.6 Vertical Text Rendering
  Swift  : CoreText CTFramesetter + CTFrameDraw into NSView,
           right-to-left CTFrameProgression, verticalGlyphForm=true.
  Python : Pillow ImageDraw.text() draws each character one at a
           time into an RGB image:
             • Columns progress right → left
             • Characters within a column progress top → bottom
             • Cell size derived from font bbox of a reference
               CJK character ("字")
             • Missing-glyph code points replaced with □ in red
             • Wrong-rotation code points drawn in red

  Limitation: Pillow does not apply OpenType GSUB features, so
  glyphs are not automatically substituted to their vertical forms
  during rendering.  The rendered preview is useful for glyph
  presence and layout; the analysis panel provides the authoritative
  vertical-support verdict.

4.7 EPUB / TXT Reading Mode
  Swift  : Manual ZIP parsing with raw byte offsets + Apple
           Compression framework for DEFLATE.
  Python : stdlib zipfile module (handles both STORE and DEFLATE).
           OPF spine order preserved via regex parsing of
           <item> / <itemref> elements.
           HTML stripped with a custom html.parser subclass that
           inserts newlines at block-level tags and decodes
           common HTML entities.
           TXT: tried utf-8, utf-8-sig, gbk, big5 in order.

4.8 GUI
  Swift  : SwiftUI (macOS only), declarative two-mode layout.
  Python : tkinter + ttk, imperative class-based layout.

  Comparison mode:
    • Top area: one Canvas column per loaded font, positioned with
      place() so they divide the width equally.
    • Font name label above each column; click to select.
    • Right-click context menu: "Remove <name>".
    • Bottom panel (fixed 300 px): toolbar + scrollable key-value
      details list.
    • Toolbar: font name display, Remove Font, Open Font…,
      Open Book…, Edit Text, font size Spinbox.

  Reading mode:
    • Top bar: Back button, font name, page N/M counter.
    • Full-area Canvas showing one page of vertical text.
    • Navigation:
        Click left half  → next page  (vertical-RL: earlier content
        Click right half → prev page   is on the right)
        ← / → arrow keys, Space bar.

  Drag-and-drop (tkinterdnd2):
    • .ttf / .otf / .ttc / .dfont → load font, add column
    • .epub / .txt → enter reading mode
    • Falls back to "Open Font…" / "Open Book…" dialogs if
      tkinterdnd2 is not available.

  Font size: Spinbox (12–200 pt); triggers full re-render.
  Window resize: debounced 150 ms, then re-renders all columns
  or the current reading page.


----------------------------------------------------------------
5. GIT HISTORY
----------------------------------------------------------------

Commit  22c0b07  Port: app.py + main.py — tkinter GUI
Commit  8b623a0  Port: vertical_renderer.py — Pillow rendering
Commit  fd0abb6  Port: epub_reader.py — EPUB/TXT extraction
Commit  1cd5dd1  Port: font_analyzer.py — analysis logic
Commit  6373068  Port: add requirements.txt
Commit  134a91e  Add original Swift tool and spec


----------------------------------------------------------------
6. HOW TO RUN
----------------------------------------------------------------

  # Install dependencies (one-time)
  pip install -r python_tool/requirements.txt

  # Launch
  python python_tool/main.py

Tested on: Windows 11 Home 10.0.26200, Python 3.14
Runtime output on first launch: one benign fonttools warning
  "114685 extra bytes in post.stringData array"
  (non-conforming post table in a system font; not an error)


----------------------------------------------------------------
7. KNOWN DIFFERENCES FROM THE SWIFT ORIGINAL
----------------------------------------------------------------

1. No real GSUB shaping in the renderer.
   The Swift app uses CoreText which applies GSUB features
   (including 'vert') during glyph layout.  The Python renderer
   draws Unicode code points directly with Pillow; vertical form
   substitution is not applied visually.  The analysis panel
   correctly identifies which fonts support it.

2. No pinch-to-zoom.
   The Swift app supports trackpad pinch gestures.  Python/tkinter
   has no cross-platform pinch support.  Font size is controlled
   via the Spinbox in the toolbar instead.

3. Column drop target highlight.
   The Swift app highlights the specific column being hovered
   during a drag.  tkinterdnd2 provides only window-level drop
   events, so per-column hover highlight is not implemented.

4. .dfont support.
   fonttools can open .dfont files on macOS.  On Windows this
   format is unlikely to be encountered; the code accepts the
   extension but fonttools may raise an error for some .dfont files.

================================================================

沒有留言: