-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathGraphCompose.java
More file actions
457 lines (424 loc) · 17.2 KB
/
Copy pathGraphCompose.java
File metadata and controls
457 lines (424 loc) · 17.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
package com.demcha.compose;
import com.demcha.compose.font.FontFamilyDefinition;
import com.demcha.compose.font.FontName;
import com.demcha.compose.font.FontShowcase;
import com.demcha.compose.font.DefaultFonts;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.api.MultiSectionDocument;
import com.demcha.compose.document.api.MultiSectionDocumentBuilder;
import com.demcha.compose.document.output.DocumentDebugOptions;
import com.demcha.compose.document.style.DocumentInsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Main entry point for GraphCompose document authoring.
*
* <p>
* Application code starts here and should use the canonical semantic document
* session through {@link #document()} to turn author intent into a paginated
* document.
* </p>
*
* <p>The typical runtime flow is:</p>
* <ol>
* <li>Create a semantic session with {@link #document(Path)} or {@link #document()}.</li>
* <li>Author content with {@link DocumentSession#pageFlow()},
* {@link DocumentSession#compose(java.util.function.Consumer)}, or
* {@link DocumentSession#dsl()}.</li>
* <li>Inspect {@link DocumentSession#layoutGraph()} or
* {@link DocumentSession#layoutSnapshot()} if needed.</li>
* <li>Call {@link DocumentSession#buildPdf()} or {@link DocumentSession#toPdfBytes()}.</li>
* </ol>
*
* <h3>Build a PDF file with the canonical DSL</h3>
*
* <pre>{@code
* try (DocumentSession document = GraphCompose.document(outputFile)
* .pageSize(DocumentPageSize.A4)
* .margin(24, 24, 24, 24)
* .create()) {
*
* document.pageFlow(page -> page
* .module("Summary", module -> module.paragraph("Hello GraphCompose")));
*
* document.buildPdf();
* }
* }</pre>
*
* <h3>Get bytes instead of writing to disk</h3>
*
* <pre>{@code
* try (DocumentSession document = GraphCompose.document()
* .pageSize(DocumentPageSize.A4)
* .create()) {
*
* document.pageFlow(page -> page
* .module("Summary", module -> module.paragraph("In-memory PDF")));
*
* byte[] pdfBytes = document.toPdfBytes();
* }
* }</pre>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class GraphCompose {
private GraphCompose() {
// Factory class, no instantiation
}
/**
* Starts the canonical semantic document composition flow.
*
* @return builder for creating an in-memory semantic document session
*/
public static DocumentBuilder document() {
return new DocumentBuilder(null);
}
/**
* Starts the canonical semantic document composition flow with a default output target
* used by {@link DocumentSession#buildPdf()}.
*
* @param outputFile default PDF output path for {@link DocumentSession#buildPdf()}
* @return builder for creating a semantic document session
*/
public static DocumentBuilder document(Path outputFile) {
return new DocumentBuilder(outputFile);
}
/**
* Starts a multi-section document: several independently authored
* {@link DocumentSession} sections — each with its own page size, margins,
* fonts, and page numbering — concatenated into one PDF with cross-section
* anchors, links, and bookmark outline.
*
* @return builder for assembling a multi-section document
* @since 1.9.0
*/
public static MultiSectionDocumentBuilder documents() {
return new MultiSectionDocumentBuilder(null);
}
/**
* Starts a multi-section document with a default output target used by
* {@link MultiSectionDocument#buildPdf()}.
*
* @param outputFile default PDF output path for {@link MultiSectionDocument#buildPdf()}
* @return builder for assembling a multi-section document
* @since 1.9.0
*/
public static MultiSectionDocumentBuilder documents(Path outputFile) {
return new MultiSectionDocumentBuilder(outputFile);
}
/**
* Returns the logical font families bundled with GraphCompose out of the box.
*
* <p>The returned names are the identifiers used by {@code DocumentTextStyle},
* {@code CvTheme}, and the font library. They describe what can be referenced
* immediately without registering custom font families.</p>
*/
public static List<FontName> availableFonts() {
return DefaultFonts.bundledFontNames();
}
/**
* Renders a PDF showcase of all bundled font families to disk.
*
* <p>This is primarily a discovery helper for developers choosing a family for
* {@code TextStyle}, templates, or themes.</p>
*/
public static void renderAvailableFontsPreview(Path outputFile) throws Exception {
FontShowcase.renderAvailableFontsPreview(outputFile);
}
/**
* Renders a PDF showcase of all bundled font families in memory.
*
* @return the generated preview document as PDF bytes
*/
public static byte[] renderAvailableFontsPreview() throws Exception {
return FontShowcase.renderAvailableFontsPreview();
}
// ===== Word Factory (placeholder for future) =====
// public static WordBuilder word(Path outputFile) {
// return new WordBuilder(outputFile);
// }
/**
* Fluent configuration builder for the canonical semantic document session.
*
* <p>This builder produces a {@link DocumentSession} that exposes the semantic DSL through
* {@link DocumentSession#dsl()} and keeps authoring separate from the PDF
* backend.</p>
*/
public static final class DocumentBuilder {
private final Path outputFile;
private DocumentPageSize pageSize = DocumentPageSize.A4;
private DocumentInsets margin = DocumentInsets.zero();
private boolean markdown = true;
private DocumentDebugOptions debug = DocumentDebugOptions.none();
private com.demcha.compose.document.style.DocumentColor pageBackground;
private java.util.List<com.demcha.compose.document.api.PageBackgroundFill> pageBackgrounds;
private final List<FontFamilyDefinition> customFontFamilies = new ArrayList<>();
private DocumentBuilder(Path outputFile) {
this.outputFile = outputFile;
}
/**
* Sets the page size used by the semantic canvas.
*
* @param pageSize physical page size
* @return this builder
*/
public DocumentBuilder pageSize(DocumentPageSize pageSize) {
this.pageSize = Objects.requireNonNull(pageSize, "pageSize");
return this;
}
/**
* Convenience overload for setting page dimensions in points.
*
* @param width page width in points
* @param height page height in points
* @return this builder
*/
public DocumentBuilder pageSize(double width, double height) {
return pageSize(DocumentPageSize.of(width, height));
}
/**
* Sets the semantic document margin with the public canonical spacing value.
*
* @param margin outer page margin
* @return this builder
*/
public DocumentBuilder margin(DocumentInsets margin) {
this.margin = margin == null ? DocumentInsets.zero() : margin;
return this;
}
/**
* Convenience overload for setting document margin in points.
*
* @param top top margin
* @param right right margin
* @param bottom bottom margin
* @param left left margin
* @return this builder
*/
public DocumentBuilder margin(float top, float right, float bottom, float left) {
this.margin = new DocumentInsets(top, right, bottom, left);
return this;
}
/**
* Enables or disables markdown parsing for semantic paragraph blocks.
*
* <p>When enabled, canonical paragraph rendering understands the same
* practical inline markdown subset used by the built-in template
* flow, such as bold and italic spans.</p>
*
* @param enabled {@code true} to enable markdown-aware paragraph rendering
* @return this builder
*/
public DocumentBuilder markdown(boolean enabled) {
this.markdown = enabled;
return this;
}
/**
* Enables or disables PDF guide-line overlays for convenience PDF output.
*
* <p>This option affects {@link DocumentSession#buildPdf()},
* {@link DocumentSession#writePdf(java.io.OutputStream)}, and
* {@link DocumentSession#toPdfBytes()}. It does not alter semantic layout
* geometry or layout snapshots.</p>
*
* <p>Shorthand for toggling only the guide overlay on the current
* {@link #debug(DocumentDebugOptions) debug} configuration — node-label
* settings are preserved, and the call order with {@code debug(...)}
* follows last-write-wins, exactly like the equivalent switches on
* {@code DocumentSession} and the PDF backend builder.</p>
*
* @param enabled {@code true} to draw debug guide-line overlays
* @return this builder
*/
public DocumentBuilder guideLines(boolean enabled) {
this.debug = this.debug.withGuides(enabled);
return this;
}
/**
* Configures debug overlays (guide lines and semantic node labels)
* for the session's convenience PDF output.
*
* <p>Replaces the whole debug configuration; {@code null} resets to
* {@link DocumentDebugOptions#none()}. Debug overlays draw on top of
* regular content and never alter semantic layout geometry or layout
* snapshots.</p>
*
* @param options debug overlay options, or {@code null} for none
* @return this builder
* @since 1.8.0
*/
public DocumentBuilder debug(DocumentDebugOptions options) {
this.debug = options == null ? DocumentDebugOptions.none() : options;
return this;
}
/**
* Configures a document-wide page background fill applied behind every
* fragment on every page.
*
* @param color page background color, or {@code null} to clear
* @return this builder
*/
public DocumentBuilder pageBackground(com.demcha.compose.document.style.DocumentColor color) {
this.pageBackground = color;
return this;
}
/**
* Convenience overload for {@link java.awt.Color}.
*
* @param color page background color, or {@code null} to clear
* @return this builder
*/
public DocumentBuilder pageBackground(java.awt.Color color) {
return pageBackground(color == null
? null
: com.demcha.compose.document.style.DocumentColor.of(color));
}
/**
* Configures one or more rectangular page background fills applied
* behind every fragment on every page. See
* {@link com.demcha.compose.document.api.PageBackgroundFill} and
* {@link com.demcha.compose.document.api.DocumentSession#pageBackgrounds}
* for the full semantics.
*
* <p>Calling this method with an empty list is an <b>explicit clear</b>:
* it overrides any earlier {@link #pageBackground(com.demcha.compose.document.style.DocumentColor)}
* call on the same builder and emits no page-background fragments.
* Passing {@code null} has the same effect.</p>
*
* @param fills ordered fills, or {@code null}/empty to clear
* @return this builder
*/
public DocumentBuilder pageBackgrounds(
java.util.List<com.demcha.compose.document.api.PageBackgroundFill> fills) {
this.pageBackgrounds = fills;
return this;
}
/**
* Registers a document-local font family available to text measurement and
* PDF rendering.
*
* @param definition font family definition
* @return this builder
*/
public DocumentBuilder registerFontFamily(FontFamilyDefinition definition) {
this.customFontFamilies.add(Objects.requireNonNull(definition, "definition"));
return this;
}
/**
* Registers a custom family backed by a single regular font file.
*
* @param familyName logical family identifier
* @param regular path to the regular font file
* @return this builder
*/
public DocumentBuilder registerFontFamily(FontName familyName, Path regular) {
return registerFontFamily(FontFamilyDefinition.files(familyName, regular).build());
}
/**
* Registers a custom family backed by a single regular font file.
*
* @param familyName logical family identifier
* @param regular path to the regular font file
* @return this builder
*/
public DocumentBuilder registerFontFamily(String familyName, Path regular) {
return registerFontFamily(FontName.of(familyName), regular);
}
/**
* Registers a custom family backed by regular, bold, and italic files.
*
* @param familyName logical family identifier
* @param regular path to the regular font file
* @param bold path to the bold font file
* @param italic path to the italic font file
* @return this builder
*/
public DocumentBuilder registerFontFamily(FontName familyName, Path regular, Path bold, Path italic) {
return registerFontFamily(FontFamilyDefinition.files(familyName, regular)
.boldPath(bold)
.italicPath(italic)
.build());
}
/**
* Registers a custom family backed by regular, bold, and italic files.
*
* @param familyName logical family identifier
* @param regular path to the regular font file
* @param bold path to the bold font file
* @param italic path to the italic font file
* @return this builder
*/
public DocumentBuilder registerFontFamily(String familyName, Path regular, Path bold, Path italic) {
return registerFontFamily(FontName.of(familyName), regular, bold, italic);
}
/**
* Registers a custom family backed by regular, bold, italic, and bold-italic files.
*
* @param familyName logical family identifier
* @param regular path to the regular font file
* @param bold path to the bold font file
* @param italic path to the italic font file
* @param boldItalic path to the bold-italic font file
* @return this builder
*/
public DocumentBuilder registerFontFamily(FontName familyName, Path regular, Path bold, Path italic, Path boldItalic) {
return registerFontFamily(FontFamilyDefinition.files(familyName, regular)
.boldPath(bold)
.italicPath(italic)
.boldItalicPath(boldItalic)
.build());
}
/**
* Registers a custom family backed by regular, bold, italic, and bold-italic files.
*
* @param familyName logical family identifier
* @param regular path to the regular font file
* @param bold path to the bold font file
* @param italic path to the italic font file
* @param boldItalic path to the bold-italic font file
* @return this builder
*/
public DocumentBuilder registerFontFamily(String familyName, Path regular, Path bold, Path italic, Path boldItalic) {
return registerFontFamily(FontName.of(familyName), regular, bold, italic, boldItalic);
}
/**
* Creates a mutable semantic document session.
*
* <pre>{@code
* try (var document = GraphCompose.document(Path.of("output.pdf"))
* .pageSize(DocumentPageSize.A4)
* .margin(24, 24, 24, 24)
* .create()) {
* document.pageFlow(page -> page
* .module("Summary", module -> module.paragraph("Hello GraphCompose")));
*
* document.buildPdf();
* }
* }</pre>
*
* @return a new semantic document session
*/
public DocumentSession create() {
DocumentSession session = new DocumentSession(
outputFile,
pageSize,
margin,
List.copyOf(customFontFamilies),
markdown,
debug.showGuides());
session.debug(debug);
if (pageBackgrounds != null) {
// Explicit pageBackgrounds() call wins over a prior
// pageBackground(color). Empty list = clear; see builder Javadoc.
session.pageBackgrounds(pageBackgrounds);
} else if (pageBackground != null) {
session.pageBackground(pageBackground);
}
return session;
}
}
}