Sfoglia il codice sorgente

chore(临考突击报告): 清理 jar 内 MiSans 遗留资源

金逸霄 6 giorni fa
parent
commit
0b19414597

+ 0 - 139
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFonts.java

@@ -1,139 +0,0 @@
-package cn.yunzhixue.ability.center.examsprint.infrastructure.report.pdf;
-
-import java.awt.Font;
-import java.awt.FontFormatException;
-import java.awt.GraphicsEnvironment;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-
-public final class BundledOutlookReportFonts {
-
-    private static final String DEFAULT_RESOURCE_PATH = "/fonts/MiSans-VF.ttf";
-    private static final String TEMP_FILE_NAME = "MiSans-VF.ttf";
-    private static final String AWT_FONT_PROBE_TEXT = "掌握率";
-    private static final Map<String, BundledOutlookReportFonts> CACHE = new HashMap<>();
-
-    private final List<Registration> registrations;
-
-    private BundledOutlookReportFonts(List<Registration> registrations) {
-        this.registrations = List.copyOf(registrations);
-    }
-
-    public static synchronized BundledOutlookReportFonts load() {
-        return load(DEFAULT_RESOURCE_PATH);
-    }
-
-    public static synchronized BundledOutlookReportFonts load(String resourcePath) {
-        Objects.requireNonNull(resourcePath, "resourcePath");
-
-        BundledOutlookReportFonts cached = CACHE.get(resourcePath);
-        if (cached != null && cached.isHealthy()) {
-            return cached;
-        }
-
-        File copiedFont = copyToTempFile(resourcePath);
-        registerWithAwt(resourcePath, copiedFont);
-
-        BundledOutlookReportFonts loaded = new BundledOutlookReportFonts(List.of(new Registration("MiSans", copiedFont)));
-        CACHE.put(resourcePath, loaded);
-        return loaded;
-    }
-
-    public List<Registration> registrations() {
-        return registrations;
-    }
-
-    private boolean isHealthy() {
-        return registrations.size() == 1 && isHealthy(registrations.get(0).file());
-    }
-
-    private static boolean isHealthy(File file) {
-        return file.exists() && file.isFile() && file.canRead() && file.length() > 0L;
-    }
-
-    private static File copyToTempFile(String resourcePath) {
-        try (InputStream inputStream = BundledOutlookReportFonts.class.getResourceAsStream(resourcePath)) {
-            if (inputStream == null) {
-                throw new BundledFontMissingException("Bundled font resource not found on classpath: " + resourcePath);
-            }
-
-            Path tempDirectory = Files.createTempDirectory("bundled-outlook-report-font-");
-            Path tempFile = tempDirectory.resolve(TEMP_FILE_NAME);
-            Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
-            File copiedFile = tempFile.toFile();
-            tempDirectory.toFile().deleteOnExit();
-            copiedFile.deleteOnExit();
-            if (!isHealthy(copiedFile)) {
-                throw new BundledFontUnavailableException("Bundled font copy is not readable: " + resourcePath + " -> " + tempFile);
-            }
-            return copiedFile;
-        } catch (IOException exception) {
-            throw new BundledFontUnavailableException("Failed to copy bundled font resource: " + resourcePath, exception);
-        }
-    }
-
-    private static void registerWithAwt(String resourcePath, File fontFile) {
-        try {
-            Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
-            String family = font.getFamily(Locale.ROOT);
-            boolean registered = GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
-            if (!registered && !isAwtFontUsable(family)) {
-                throw new BundledFontUnavailableException(
-                        "Failed to register bundled font with Java AWT: " + resourcePath + " -> " + fontFile);
-            }
-            if (!isAwtFontUsable(family)) {
-                throw new BundledFontUnavailableException(
-                        "Bundled font registered with Java AWT but cannot display report CJK text: "
-                                + resourcePath + " -> " + fontFile + " (family: " + family + ")");
-            }
-        } catch (FontFormatException exception) {
-            throw new BundledFontUnavailableException(
-                    "Bundled font resource is not a valid TrueType font for Java AWT: " + resourcePath + " -> " + fontFile,
-                    exception);
-        } catch (IOException exception) {
-            throw new BundledFontUnavailableException(
-                    "Failed to load bundled font into Java AWT: " + resourcePath + " -> " + fontFile,
-                    exception);
-        }
-    }
-
-    private static boolean isAwtFontUsable(String family) {
-        return family != null && !family.isBlank()
-                && new Font(family, Font.PLAIN, 12).canDisplayUpTo(AWT_FONT_PROBE_TEXT) == -1;
-    }
-
-    static class BundledFontUnavailableException extends RuntimeException {
-
-        BundledFontUnavailableException(String message) {
-            super(message);
-        }
-
-        BundledFontUnavailableException(String message, Throwable cause) {
-            super(message, cause);
-        }
-    }
-
-    static final class BundledFontMissingException extends BundledFontUnavailableException {
-
-        BundledFontMissingException(String message) {
-            super(message);
-        }
-    }
-
-    public record Registration(String family, File file) {
-        public Registration {
-            Objects.requireNonNull(family, "family");
-            Objects.requireNonNull(file, "file");
-        }
-    }
-
-}

+ 2 - 7
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGenerator.java

@@ -13,7 +13,6 @@ import org.springframework.beans.factory.DisposableBean;
 import org.springframework.stereotype.Component;
 
 import java.util.Objects;
-import java.util.function.Supplier;
 
 @Component
 public class PlaywrightExamSprintReportPdfGenerator implements ExamSprintReportPdfGenerator, DisposableBean, AutoCloseable {
@@ -29,14 +28,10 @@ public class PlaywrightExamSprintReportPdfGenerator implements ExamSprintReportP
     private boolean closed;
 
     public PlaywrightExamSprintReportPdfGenerator() {
-        this(BundledOutlookReportFonts::load, DEFAULT_LAUNCH_TIMEOUT_MILLIS, DEFAULT_RENDER_TIMEOUT_MILLIS);
+        this(DEFAULT_LAUNCH_TIMEOUT_MILLIS, DEFAULT_RENDER_TIMEOUT_MILLIS);
     }
 
-    PlaywrightExamSprintReportPdfGenerator(
-            Supplier<BundledOutlookReportFonts> bundledFontsSupplier,
-            double launchTimeoutMillis,
-            double renderTimeoutMillis) {
-        Objects.requireNonNull(bundledFontsSupplier, "bundledFontsSupplier");
+    PlaywrightExamSprintReportPdfGenerator(double launchTimeoutMillis, double renderTimeoutMillis) {
         this.launchTimeoutMillis = launchTimeoutMillis;
         this.renderTimeoutMillis = renderTimeoutMillis;
     }

BIN
abilities/exam-sprint/infrastructure/src/main/resources/fonts/MiSans-VF.ttf


+ 0 - 63
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFontsTest.java

@@ -1,63 +0,0 @@
-package cn.yunzhixue.ability.center.examsprint.infrastructure.report.pdf;
-
-import org.junit.jupiter.api.Test;
-
-import java.awt.Font;
-import java.awt.GraphicsEnvironment;
-import java.nio.file.Files;
-import java.util.Arrays;
-import java.util.Locale;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-class BundledOutlookReportFontsTest {
-
-    @Test
-    void loadReturnsCachedInstanceWithOneMiSansRegistrationWhenHealthy() {
-        BundledOutlookReportFonts first = BundledOutlookReportFonts.load();
-        BundledOutlookReportFonts second = BundledOutlookReportFonts.load();
-
-        assertThat(second).isSameAs(first);
-        assertThat(first.registrations()).hasSize(1);
-        BundledOutlookReportFonts.Registration registration = first.registrations().get(0);
-        assertThat(registration.family()).isEqualTo("MiSans");
-        assertThat(registration.file().getName()).isEqualTo("MiSans-VF.ttf");
-        assertThat(registration.file()).exists().isFile().canRead();
-        assertThat(registration.file().length()).isGreaterThan(0L);
-    }
-
-    @Test
-    void loadRegistersBundledMiSansWithAwtGraphicsEnvironmentForBatikSvgText() {
-        BundledOutlookReportFonts.load();
-
-        String[] availableFontFamilies = GraphicsEnvironment.getLocalGraphicsEnvironment()
-                .getAvailableFontFamilyNames(Locale.ROOT);
-
-        assertThat(Arrays.asList(availableFontFamilies)).contains("MiSans VF");
-        assertThat(new Font("MiSans VF", Font.PLAIN, 24).canDisplayUpTo("掌握率")).isEqualTo(-1);
-    }
-
-    @Test
-    void loadRecreatesTempCopyWhenCachedFileIsRemoved() throws Exception {
-        BundledOutlookReportFonts first = BundledOutlookReportFonts.load();
-        BundledOutlookReportFonts.Registration firstRegistration = first.registrations().get(0);
-        Files.delete(firstRegistration.file().toPath());
-
-        BundledOutlookReportFonts second = BundledOutlookReportFonts.load();
-        BundledOutlookReportFonts.Registration secondRegistration = second.registrations().get(0);
-
-        assertThat(secondRegistration.family()).isEqualTo("MiSans");
-        assertThat(secondRegistration.file().getName()).isEqualTo("MiSans-VF.ttf");
-        assertThat(secondRegistration.file()).exists().isFile().canRead();
-        assertThat(secondRegistration.file().length()).isGreaterThan(0L);
-    }
-
-    @Test
-    void loadFailsWithClearMessageWhenClasspathResourceIsMissing() {
-        assertThatThrownBy(() -> BundledOutlookReportFonts.load("/fonts/does-not-exist.ttf"))
-                .isInstanceOf(BundledOutlookReportFonts.BundledFontMissingException.class)
-                .hasMessageContaining("/fonts/does-not-exist.ttf")
-                .hasMessageContaining("Bundled font resource not found on classpath");
-    }
-}

+ 1 - 4
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGeneratorTest.java

@@ -48,10 +48,7 @@ class PlaywrightExamSprintReportPdfGeneratorTest {
     @Test
     void constructorDoesNotLaunchChromiumBeforeFirstGenerate() {
         assertThatCode(() -> {
-            try (PlaywrightExamSprintReportPdfGenerator generator = new PlaywrightExamSprintReportPdfGenerator(
-                    BundledOutlookReportFonts::load,
-                    1,
-                    1)) {
+            try (PlaywrightExamSprintReportPdfGenerator generator = new PlaywrightExamSprintReportPdfGenerator(1, 1)) {
                 // Constructor should not require a browser launch; Chromium is initialized lazily by generate().
             }
         }).doesNotThrowAnyException();

+ 1 - 1
docs/superpowers/plans/2026-05-07-playwright-pdf-generator.md

@@ -53,7 +53,7 @@ Expected: compile succeeds or fails only because the new Playwright implementati
   - Lazily creates one headless Chromium `Browser` during the first `generate()` call so Spring context startup does not require Chromium to launch.
   - Serializes Playwright calls using a private lock.
   - Creates and closes a fresh `BrowserContext` per PDF.
-  - Injects bundled MiSans as `MiSans`, `MiSans VF`, and `ReportFont` through a `file:` URL `@font-face` block when the bundled font is available.
+  - Relies on the runtime Docker image installing MiSans as a system font so Chromium can resolve the report font stack (`'MiSans VF', MiSans, ReportFont, sans-serif`).
   - Waits for `document.fonts.ready` before `page.pdf()`.
   - Uses A4, print media, CSS page size preference, and background printing.
   - Wraps generation failures in `IllegalStateException("Failed to generate PDF", exception)`.

+ 240 - 0
docs/superpowers/plans/2026-05-08-cleanup-bundled-misans.md

@@ -0,0 +1,240 @@
+# Cleanup Bundled MiSans Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Remove the unused jar-bundled MiSans font path and keep Docker runtime system MiSans as the single PDF font source.
+
+**Architecture:** The Playwright PDF generator will no longer accept or reference bundled font loaders. Runtime PDF rendering continues to rely on the existing CSS font stack and the Docker image installing `MiSans-VF.ttf` into the OS font cache.
+
+**Tech Stack:** Java 17, Spring Boot 3.3.5, Maven multi-module project, Playwright Java, Docker runtime image.
+
+---
+
+## File Structure
+
+- Delete `abilities/exam-sprint/infrastructure/src/main/resources/fonts/MiSans-VF.ttf`: unused classpath font resource.
+- Delete `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFonts.java`: unused bundled font loader.
+- Delete `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFontsTest.java`: tests for deleted loader.
+- Modify `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGenerator.java`: simplify constructors and remove `Supplier<BundledOutlookReportFonts>` dependency.
+- Modify `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGeneratorTest.java`: update test-only constructor usage.
+- Modify `docs/superpowers/plans/2026-05-07-playwright-pdf-generator.md`: replace stale bundled-font wording with runtime-system-font wording.
+
+### Task 1: Simplify Playwright Generator Constructors
+
+**Files:**
+- Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGenerator.java`
+- Modify: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGeneratorTest.java`
+
+- [ ] **Step 1: Update the test constructor call**
+
+In `PlaywrightExamSprintReportPdfGeneratorTest.java`, replace the constructor call in `constructorDoesNotLaunchChromiumBeforeFirstGenerate()` from:
+
+```java
+try (PlaywrightExamSprintReportPdfGenerator generator = new PlaywrightExamSprintReportPdfGenerator(
+        BundledOutlookReportFonts::load,
+        1,
+        1)) {
+```
+
+to:
+
+```java
+try (PlaywrightExamSprintReportPdfGenerator generator = new PlaywrightExamSprintReportPdfGenerator(1, 1)) {
+```
+
+- [ ] **Step 2: Run the targeted compile/test to verify the expected failure**
+
+Run:
+
+```bash
+mvn -pl abilities/exam-sprint/infrastructure -am -Dtest=PlaywrightExamSprintReportPdfGeneratorTest test
+```
+
+Expected: compilation fails because `PlaywrightExamSprintReportPdfGenerator(double, double)` does not exist yet.
+
+- [ ] **Step 3: Simplify the production class constructors**
+
+In `PlaywrightExamSprintReportPdfGenerator.java`, remove this import:
+
+```java
+import java.util.Objects;
+import java.util.function.Supplier;
+```
+
+Add back only this import if `Objects` is still needed by another statement:
+
+```java
+import java.util.Objects;
+```
+
+Then replace the constructors:
+
+```java
+public PlaywrightExamSprintReportPdfGenerator() {
+    this(BundledOutlookReportFonts::load, DEFAULT_LAUNCH_TIMEOUT_MILLIS, DEFAULT_RENDER_TIMEOUT_MILLIS);
+}
+
+PlaywrightExamSprintReportPdfGenerator(
+        Supplier<BundledOutlookReportFonts> bundledFontsSupplier,
+        double launchTimeoutMillis,
+        double renderTimeoutMillis) {
+    Objects.requireNonNull(bundledFontsSupplier, "bundledFontsSupplier");
+    this.launchTimeoutMillis = launchTimeoutMillis;
+    this.renderTimeoutMillis = renderTimeoutMillis;
+}
+```
+
+with:
+
+```java
+public PlaywrightExamSprintReportPdfGenerator() {
+    this(DEFAULT_LAUNCH_TIMEOUT_MILLIS, DEFAULT_RENDER_TIMEOUT_MILLIS);
+}
+
+PlaywrightExamSprintReportPdfGenerator(double launchTimeoutMillis, double renderTimeoutMillis) {
+    this.launchTimeoutMillis = launchTimeoutMillis;
+    this.renderTimeoutMillis = renderTimeoutMillis;
+}
+```
+
+Keep this line in `generate(String htmlContent)` unchanged, so `java.util.Objects` remains required:
+
+```java
+Objects.requireNonNull(htmlContent, "htmlContent");
+```
+
+- [ ] **Step 4: Run the targeted generator test**
+
+Run:
+
+```bash
+mvn -pl abilities/exam-sprint/infrastructure -am -Dtest=PlaywrightExamSprintReportPdfGeneratorTest test
+```
+
+Expected: the test compiles and passes, assuming local Playwright Chromium is installed and available.
+
+### Task 2: Delete Bundled Font Loader and Resource
+
+**Files:**
+- Delete: `abilities/exam-sprint/infrastructure/src/main/resources/fonts/MiSans-VF.ttf`
+- Delete: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFonts.java`
+- Delete: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFontsTest.java`
+
+- [ ] **Step 1: Delete the classpath font resource**
+
+Delete:
+
+```text
+abilities/exam-sprint/infrastructure/src/main/resources/fonts/MiSans-VF.ttf
+```
+
+- [ ] **Step 2: Delete the unused bundled font loader**
+
+Delete:
+
+```text
+abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFonts.java
+```
+
+- [ ] **Step 3: Delete the loader test**
+
+Delete:
+
+```text
+abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/BundledOutlookReportFontsTest.java
+```
+
+- [ ] **Step 4: Verify no production/test references remain**
+
+Run:
+
+```bash
+rg "BundledOutlookReportFonts|/fonts/MiSans-VF.ttf|src/main/resources/fonts/MiSans-VF.ttf" abilities/exam-sprint/infrastructure
+```
+
+Expected: no matches.
+
+### Task 3: Update Stale Plan Documentation
+
+**Files:**
+- Modify: `docs/superpowers/plans/2026-05-07-playwright-pdf-generator.md`
+
+- [ ] **Step 1: Replace the stale bundled-font bullet**
+
+In `docs/superpowers/plans/2026-05-07-playwright-pdf-generator.md`, replace this line:
+
+```markdown
+  - Injects bundled MiSans as `MiSans`, `MiSans VF`, and `ReportFont` through a `file:` URL `@font-face` block when the bundled font is available.
+```
+
+with:
+
+```markdown
+  - Relies on the runtime Docker image installing MiSans as a system font so Chromium can resolve the report font stack (`'MiSans VF', MiSans, ReportFont, sans-serif`).
+```
+
+- [ ] **Step 2: Verify the docs no longer describe bundled font injection**
+
+Run:
+
+```bash
+rg "bundled MiSans|@font-face|/fonts/MiSans-VF.ttf|BundledOutlookReportFonts" docs/superpowers/plans/2026-05-07-playwright-pdf-generator.md
+```
+
+Expected: no matches.
+
+### Task 4: Final Verification
+
+**Files:**
+- No source file changes.
+
+- [ ] **Step 1: Run targeted infrastructure tests**
+
+Run:
+
+```bash
+mvn -pl abilities/exam-sprint/infrastructure -am -Dtest=PlaywrightExamSprintReportPdfGeneratorTest test
+```
+
+Expected: build exits with code 0 and `PlaywrightExamSprintReportPdfGeneratorTest` passes.
+
+- [ ] **Step 2: Run broader PDF/report related tests if time allows**
+
+Run:
+
+```bash
+mvn -pl abilities/exam-sprint/infrastructure -am -Dtest='*Report*Test,*Pdf*Test' test
+```
+
+Expected: build exits with code 0. If Playwright Chromium is missing locally, install it with the existing project command before rerunning:
+
+```bash
+mvn -pl abilities/exam-sprint/infrastructure exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install chromium"
+```
+
+- [ ] **Step 3: Verify the Docker runtime font remains available in source**
+
+Run:
+
+```bash
+rg "MiSans-VF.ttf|fc-match \"MiSans VF\"" deploy/ability-center/runtime
+```
+
+Expected: matches remain in `deploy/ability-center/runtime/dockerfile` and the only remaining source font file is `deploy/ability-center/runtime/fonts/MiSans-VF.ttf`.
+
+- [ ] **Step 4: Inspect git diff**
+
+Run:
+
+```bash
+git diff --stat && git diff -- abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGenerator.java abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/pdf/PlaywrightExamSprintReportPdfGeneratorTest.java docs/superpowers/plans/2026-05-07-playwright-pdf-generator.md
+```
+
+Expected: diff only removes bundled font artifacts, simplifies the Playwright generator constructor, updates the related test call, and fixes stale documentation.
+
+## Self-Review
+
+- Spec coverage: The plan deletes the jar-bundled font, removes the unused loader and tests, simplifies the only production constructor reference, updates the stale documentation, and preserves Docker runtime system font handling.
+- Placeholder scan: No `TBD`, `TODO`, or undefined implementation instructions remain.
+- Type consistency: The new test call uses `PlaywrightExamSprintReportPdfGenerator(double, double)`, which Task 1 defines as a package-private constructor matching the current test package.
+- Commit policy: No commit step is included because the current session has not received an explicit request to create a git commit.