跳至主要内容

Flow 報表:表格虛線與每頁表身底線

虛線與每頁底線適用版本:io.leandev.appfuse.webapp:appfuse-server:4.0.0-alpha.31。 續頁切點配置與每頁段後距自 4.0.0-alpha.32 提供;以下範例 pin 此不可變版本。 本頁說明公開配置;正式 PDF 仍由 ReportDocument/Section/ReportRenderer 產生。

公開 API 查核​

2026-10-11 核對 Maven releases metadata,最新發布版為 4.0.0-alpha.30,並下載正式 JAR 以 javap 核對: LineStyle 只有 Length width/ReportColor color;CellBorders、TableRowStyle 使用同一型別。 TableStyle.rule 是逐列下線,無法根據 renderer 實際換頁覆寫表身最後一條線;儲存格邊線也無法辨認頁末。 SectionPageRegion.rule 位於頁首尾區域,並非每個表格片段的底緣。SVG 虛線只在 Figure 內,不能配置表格邊線。 因此 alpha.30 的既有公開 API 不能等價組合本能力。

快速開始​

implementation("io.leandev.appfuse.webapp:appfuse-server:4.0.0-alpha.32")
implementation("org.apache.pdfbox:pdfbox:3.0.8")
import io.leandev.appfuse.measure.Length;
import io.leandev.appfuse.report.node.LineStyle;
import io.leandev.appfuse.report.node.ReportColor;
import io.leandev.appfuse.report.node.CellBorders;
import io.leandev.appfuse.report.node.TableRowStyle;
import io.leandev.appfuse.report.node.TableStyle;
import java.util.List;

var solid = new LineStyle(Length.ofPoint(0.5), ReportColor.rgb(0));
var dashed = solid.dashed(); // 2pt 線段、2pt 空白、phase 0
var custom = solid.withDash(List.of(Length.ofPoint(3), Length.ofPoint(2)), Length.ofPoint(1));
var diseaseEnd = TableRowStyle.noRules().withBottomRule(dashed);
var cellBottom = CellBorders.none().withBottom(dashed);
var header = TableRowStyle.noRules().withTopRule(solid).withBottomRule(solid);
var table = TableStyle.inherit().withCellBorders(CellBorders.none()).withPageBottomRule(solid);

配置選項​

API預設及合法值
new LineStyle(width, color)保留既有建構器;空 dash pattern、phase 0,畫實線
new LineStyle(width, color, dashPattern, dashPhase)pattern/phase 不可 null;清單不可含 null,建構時防禦複製
withDash(List<Length>, Length)交替線段/空白長度;空清單為實線且 phase 必須 0;非空清單至少一個元素大於 0
dashed()2pt 線段、2pt 空白、phase 0;保留線寬與顏色
solid()清空 pattern、phase 0;保留線寬與顏色
withPageBottomRule(LineStyle)預設 null;每個含表身的表格頁片段末端覆寫線,包括最後頁;零寬停用覆寫

pattern、phase 都是實體長度,可用 pt/mm/inch 等 Length 工廠,繪製時換成 PDF pt(1/72 inch), 不是像素或線寬倍數。值必須有限、非負,轉成 PDF 單精度 pt 後不得溢位或把正值縮成 0; 非法配置在建構時丟 IllegalArgumentException,null 丟 NullPointerException。 Length 已拒絕負值、NaN 及 Infinity;額外驗證涵蓋單位轉換與 PDF 精度。 奇數個元素依 PDF 語意重複兩次形成完整週期;允許個別零長度但不能全零。 phase 預設 0,可大於週期,依週期循環;每次獨立邊線/線段重新使用指定 phase,不承諾相鄰格的虛線連續。

TableStyle.pageBottomRule 沿文件 → 節/區塊 → 表格的既有 overlay 繼承。 null 表示繼承,沒有上層設定時不覆寫;零寬可明確停用上層覆寫,保留原列/儲存格底線。 啟用後,表身最後片段底緣的列水平線與儲存格水平邊被取代,再畫表格全寬線; 垂直邊、其他高度的線與其他表格維持原樣。空表格只保留表頭線,不另加表身底線。 每頁含兩張表時,兩張表各自在自己的底緣收尾,並非統一畫在紙張頁尾。

只在表格續頁切點覆寫底線​

新增選填配置 withPageBottomRuleOnContinuationOnly(true)。未設定或設定 false 時, withPageBottomRule 維持 alpha.31 的每個片段末端覆寫行為;null 繼承、false 可覆寫上層 true。

var continuationTable = TableStyle.inherit().withPageBottomRule(solid)
.withPageBottomRuleOnContinuationOnly(true);

true 只在表格仍有後續內容、renderer 必須於表格內換頁時覆寫;自然結束的儲存格邊線及列線原樣保留。 此判定涵蓋一般列、可拆列、不可拆紀錄、重複表頭和跨列合併;表格後其他區塊的換頁不算表格續頁。 空表格不新增底線。線條不增加排版高度,也不決定日期是否與疾病同頁。

RPT-014 的日期可作為表格末列:日期列設 recordStart(false)、splittable(false)、TableRowStyle.noRules() 及 CellBorders.none(),併入最後疾病的不可拆紀錄;表格配置正 maxRecordsPerPage。 最後疾病儲存格配置實線底邊,日期格不配置底線。這樣末疾病與日期一起移頁,線在疾病之後、日期之前, 自然結束不會再於日期之下新增線。合併格只跨疾病的檢驗列,勿跨到日期列。 examples/table-lines/TableLinesPaginationExample.java 提供完整配置。

多列疾病分組​

同一疾病的兩個檢驗項目以 recordStart 組成一筆不可拆紀錄, 疾病名稱、結果及情境備註以既有 Cell.withRowSpan(2) 合併。 組內列使用 TableRowStyle.noRules(),只在疾病末列配置全寬虛線:

import io.leandev.appfuse.report.node.Cell;
import io.leandev.appfuse.report.node.TableRow;

var first = TableRow.detail(Cell.of("疾病甲").withRowSpan(2), Cell.of("項目一"),
Cell.of("陰性").withRowSpan(2), Cell.of("情境備註").withRowSpan(2))
.withSplittable(false).withStyle(TableRowStyle.noRules());
var second = TableRow.detail("項目二").withRecordStart(false).withSplittable(false)
.withStyle(TableRowStyle.noRules().withBottomRule(dashed));
// 四欄表、columnTitles(false)、repeatHeader(true)、maxRecordsPerPage(10000)
// 指定 headerRow 的上下線為 solid,表格 style 配置 withPageBottomRule(solid)。

跨列仍限正 maxRecordsPerPage 的同筆不可拆 DETAIL 紀錄、跨度 1~256;本次不擴大跨頁合併格限制。 當最後一疾病剛好在頁末,末列虛線由實線取代;同頁的疾病間仍畫虛線。 表頭上下線沿既有 headerRow 配置並每頁重複;指定表與自選表使用同一配置方式。 一般可拆列亦支援每頁表身底線,不增加內距、欄寬、列高、換行或分頁所需的空間。

PDF renderer 對虛線 Rule/Border/Rect 保存與還原 graphics state; 後續實線、Figure、文字、頁面回填不受 dash 狀態影響。 未設定新能力走既有實線繪製路徑。保留雙參數 LineStyle 與七/八參數 TableStyle 建構器; 新增 record component 仍影響反射、模式解構、equals/hashCode/toString,需依消費端使用方式評估。

可執行範例與驗證​

完整公開 API 範例可下載 ZIP,原始碼位於 appfuse-server/examples/table-lines,JDK 25、Gradle 9.2.1、PDFBox 3.0.8。 一般下游固定用已發布版號,不傳 candidate:

../../gradlew run --args='/absolute/font-directory /absolute/output-directory'

字型目錄包含合法 NotoSans-Regular.ttf、NotoSans-Bold.ttf、NotoSansTC-Regular.ttf、NotoSansTC-Bold.ttf; 字型不隨範例散布。產生完整報告、長名稱、8,000字備註、指定/自選續頁、100案批次五份合成 PDF。 框架維護者發布前可加 -Pcandidate 使用本機 composite build。

2026-10-11 全量框架驗證:1,715項、0失敗、9略過;Javadoc、文件建置及公開import/llms索引檢查通過。 框架測試檢查混合線型的實際 PDF 線寬/顏色/dash/phase、graphics state 隔離、 跨列整組底線、續頁與同頁多表末端覆寫、空表及 opt-out;並比較線型更改前後的文字座標與頁數。 同一JDK25/PDFBox3.0.8/字型環境的五案例頁數依序為1、1、3、4、100; 逐字Unicode、x/y、寬/高及頁碼完全一致。既有實線範例以正式alpha.30與候選框架各自編譯產生的PDF bytes完全相同, alpha.30編譯的consumer直接載入候選框架亦相同。 這些合成驗證不代表 nbsos RPT-014 revision 12 已與確認的 HTML 對齊或取得真人 PDF 接受。 下游 pin 發布版後,沿 revision 12 的字型、欄寬、內距、行高及頁面設定實作, 接續完整報告/長名稱/8,000字/100案的逐頁 HTML 與 PDF 比對。

分頁片段驗證(alpha.32)​

RPT-014 新配置以人類已確認的 revision 17 固定包與包內字型驗證;revision 16 留作未啟用配置的舊行為基準。 在暫存消費端副本只新增兩個公開 API 配置,保留原 6mm 頁尾預留與其他字型、欄寬及間距,未修改 nbsos 工作樹。 直接使用 Maven alpha.32(未使用 composite build)的 13 項檢查通過,十組非空案例逐頁文字及空白一致,橫線位置與線型符合 1mm 容差,頁首欄位位置符合 0.2mm 容差。 容量完整 case 的 8,000 UTF-16 備註仍為 7 頁,各頁行數 2、52、52、52、52、52、35, 逐行基線與段落片段起點亦符合 1mm 容差;包括長名稱、合併格、不可拆紀錄、指定/自選表及 100 案批次。 未啟用新配置的五份公開範例與正式 alpha.31 PDF bytes 全部相同。

alpha.32 全量框架測試 1,725 項、0 失敗、9 略過,Javadoc 與文檔建置通過。 驗證紀錄位於下載包的 verification-fragments.json,包含固定 HTML oracle 及字型雜湊。 此為框架能力及暫存消費端的自動化證據;nbsos 正式升級 pin 與真人 PDF 接受仍由應用端承接。

API 參考​