diff --git a/CHANGELOG.md b/CHANGELOG.md index 2405fb61c..347361daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,16 @@ follow semantic versioning; release dates are ISO 8601. ### Fixed +- **The example catalogue renders the weights it declares.** The same defect the donut + KPI had, 138 times over in the examples: a font *face* constant named with no + decoration, which the library rewrites to its base family before the lookup. The PPTX + backend began reading the face from the decoration in the 2.1 line, so decks that had + rendered bold went out regular — a live loss on `develop`, invisible because nothing + re-rendered the committed previews to show it. Restoring the weights changed four + decks back and gave a fifth the emphasis it had always asked for. Thirty-one committed + previews and the README hero are re-rendered; the weekly schedule's columns are recomputed to fill the + printable width, because bold day notes no longer fit a span over fixed sub-columns and + a spanned cell cannot borrow width from them. - **The donut-centre KPI renders the weight it declares.** A style names a font *family* and a *decoration*, and the decoration is what picks the face within the family — the standard-14 face constants (`HELVETICA_BOLD`, `TIMES_ITALIC`, …) are diff --git a/assets/readme/examples/barcode-showcase.pdf b/assets/readme/examples/barcode-showcase.pdf index 42e87e5ca..8154e8371 100644 Binary files a/assets/readme/examples/barcode-showcase.pdf and b/assets/readme/examples/barcode-showcase.pdf differ diff --git a/assets/readme/examples/business-report.pdf b/assets/readme/examples/business-report.pdf index e3a7c4460..1caf46035 100644 Binary files a/assets/readme/examples/business-report.pdf and b/assets/readme/examples/business-report.pdf differ diff --git a/assets/readme/examples/business-report.pptx b/assets/readme/examples/business-report.pptx index 7537512be..91ed26470 100644 Binary files a/assets/readme/examples/business-report.pptx and b/assets/readme/examples/business-report.pptx differ diff --git a/assets/readme/examples/canvas-layer-showcase.pdf b/assets/readme/examples/canvas-layer-showcase.pdf index 2c034fef9..c8df82f9a 100644 Binary files a/assets/readme/examples/canvas-layer-showcase.pdf and b/assets/readme/examples/canvas-layer-showcase.pdf differ diff --git a/assets/readme/examples/composed-table-cell-showcase.pdf b/assets/readme/examples/composed-table-cell-showcase.pdf index fa71b4bc1..d3ff47c73 100644 Binary files a/assets/readme/examples/composed-table-cell-showcase.pdf and b/assets/readme/examples/composed-table-cell-showcase.pdf differ diff --git a/assets/readme/examples/cover-letter.pdf b/assets/readme/examples/cover-letter.pdf index bd5ed48a8..6c8ad8498 100644 Binary files a/assets/readme/examples/cover-letter.pdf and b/assets/readme/examples/cover-letter.pdf differ diff --git a/assets/readme/examples/emoji-shortcodes.pdf b/assets/readme/examples/emoji-shortcodes.pdf index 32910dedb..05a139440 100644 Binary files a/assets/readme/examples/emoji-shortcodes.pdf and b/assets/readme/examples/emoji-shortcodes.pdf differ diff --git a/assets/readme/examples/engine-deck-v2.pdf b/assets/readme/examples/engine-deck-v2.pdf index 05ad65617..323275d0b 100644 Binary files a/assets/readme/examples/engine-deck-v2.pdf and b/assets/readme/examples/engine-deck-v2.pdf differ diff --git a/assets/readme/examples/engine-deck.pdf b/assets/readme/examples/engine-deck.pdf index 1e1c1872c..adf4488f3 100644 Binary files a/assets/readme/examples/engine-deck.pdf and b/assets/readme/examples/engine-deck.pdf differ diff --git a/assets/readme/examples/feature-catalog.pdf b/assets/readme/examples/feature-catalog.pdf index 172c99908..0c0e23917 100644 Binary files a/assets/readme/examples/feature-catalog.pdf and b/assets/readme/examples/feature-catalog.pdf differ diff --git a/assets/readme/examples/financial-report.pdf b/assets/readme/examples/financial-report.pdf index afa1c729f..8a0f47071 100644 Binary files a/assets/readme/examples/financial-report.pdf and b/assets/readme/examples/financial-report.pdf differ diff --git a/assets/readme/examples/financial-report.pptx b/assets/readme/examples/financial-report.pptx index 3c8c4cf0a..7744cf16d 100644 Binary files a/assets/readme/examples/financial-report.pptx and b/assets/readme/examples/financial-report.pptx differ diff --git a/assets/readme/examples/inline-code-column-wrap.pdf b/assets/readme/examples/inline-code-column-wrap.pdf index c4f1ee395..50102276d 100644 Binary files a/assets/readme/examples/inline-code-column-wrap.pdf and b/assets/readme/examples/inline-code-column-wrap.pdf differ diff --git a/assets/readme/examples/inline-highlight-chips.pdf b/assets/readme/examples/inline-highlight-chips.pdf index 51018b0be..fd23b32c3 100644 Binary files a/assets/readme/examples/inline-highlight-chips.pdf and b/assets/readme/examples/inline-highlight-chips.pdf differ diff --git a/assets/readme/examples/inline-shapes.pdf b/assets/readme/examples/inline-shapes.pdf index 4b2acbf52..55980ce32 100644 Binary files a/assets/readme/examples/inline-shapes.pdf and b/assets/readme/examples/inline-shapes.pdf differ diff --git a/assets/readme/examples/inline-svg-icons.pdf b/assets/readme/examples/inline-svg-icons.pdf index 546f65db4..ff94d45f7 100644 Binary files a/assets/readme/examples/inline-svg-icons.pdf and b/assets/readme/examples/inline-svg-icons.pdf differ diff --git a/assets/readme/examples/invoice-cinematic.pdf b/assets/readme/examples/invoice-cinematic.pdf index 7e26d95f9..940b26dc5 100644 Binary files a/assets/readme/examples/invoice-cinematic.pdf and b/assets/readme/examples/invoice-cinematic.pdf differ diff --git a/assets/readme/examples/invoice-http-stream.pdf b/assets/readme/examples/invoice-http-stream.pdf index bd6debdf6..42d6acbb8 100644 Binary files a/assets/readme/examples/invoice-http-stream.pdf and b/assets/readme/examples/invoice-http-stream.pdf differ diff --git a/assets/readme/examples/invoice-snapshot-regression.pdf b/assets/readme/examples/invoice-snapshot-regression.pdf index 900ebf16d..2a251d961 100644 Binary files a/assets/readme/examples/invoice-snapshot-regression.pdf and b/assets/readme/examples/invoice-snapshot-regression.pdf differ diff --git a/assets/readme/examples/master-showcase.pdf b/assets/readme/examples/master-showcase.pdf index e2d503041..b0f1a8ce6 100644 Binary files a/assets/readme/examples/master-showcase.pdf and b/assets/readme/examples/master-showcase.pdf differ diff --git a/assets/readme/examples/master-showcase.pptx b/assets/readme/examples/master-showcase.pptx index c11a026e2..6f1a7686b 100644 Binary files a/assets/readme/examples/master-showcase.pptx and b/assets/readme/examples/master-showcase.pptx differ diff --git a/assets/readme/examples/maven-banner.pdf b/assets/readme/examples/maven-banner.pdf index ffebe942b..97d307fcf 100644 Binary files a/assets/readme/examples/maven-banner.pdf and b/assets/readme/examples/maven-banner.pdf differ diff --git a/assets/readme/examples/maven-banner.pptx b/assets/readme/examples/maven-banner.pptx index 87d006deb..61e8386cc 100644 Binary files a/assets/readme/examples/maven-banner.pptx and b/assets/readme/examples/maven-banner.pptx differ diff --git a/assets/readme/examples/nested-list-showcase.pdf b/assets/readme/examples/nested-list-showcase.pdf index 6006aea28..68d1dbe68 100644 Binary files a/assets/readme/examples/nested-list-showcase.pdf and b/assets/readme/examples/nested-list-showcase.pdf differ diff --git a/assets/readme/examples/pdf-chrome.pdf b/assets/readme/examples/pdf-chrome.pdf index d3f332da1..c6253c1d8 100644 Binary files a/assets/readme/examples/pdf-chrome.pdf and b/assets/readme/examples/pdf-chrome.pdf differ diff --git a/assets/readme/examples/rich-text-showcase.pdf b/assets/readme/examples/rich-text-showcase.pdf index 87da3a114..e2f3952f2 100644 Binary files a/assets/readme/examples/rich-text-showcase.pdf and b/assets/readme/examples/rich-text-showcase.pdf differ diff --git a/assets/readme/examples/section-presets.pdf b/assets/readme/examples/section-presets.pdf index ed0006af9..85f2ac1ca 100644 Binary files a/assets/readme/examples/section-presets.pdf and b/assets/readme/examples/section-presets.pdf differ diff --git a/assets/readme/examples/twin-output.pdf b/assets/readme/examples/twin-output.pdf index 9cc9d65e8..079213511 100644 Binary files a/assets/readme/examples/twin-output.pdf and b/assets/readme/examples/twin-output.pdf differ diff --git a/assets/readme/examples/twin-output.pptx b/assets/readme/examples/twin-output.pptx index 6dc69bf47..b3e52af89 100644 Binary files a/assets/readme/examples/twin-output.pptx and b/assets/readme/examples/twin-output.pptx differ diff --git a/assets/readme/examples/weekly-schedule.pdf b/assets/readme/examples/weekly-schedule.pdf index e25e4e0cf..d85aaaff1 100644 Binary files a/assets/readme/examples/weekly-schedule.pdf and b/assets/readme/examples/weekly-schedule.pdf differ diff --git a/assets/readme/examples/word-export-companion.pdf b/assets/readme/examples/word-export-companion.pdf index 77ba11cdf..ca4f52a8e 100644 Binary files a/assets/readme/examples/word-export-companion.pdf and b/assets/readme/examples/word-export-companion.pdf differ diff --git a/assets/readme/repository_showcase_render.png b/assets/readme/repository_showcase_render.png index db9333d47..b7e455dcd 100644 Binary files a/assets/readme/repository_showcase_render.png and b/assets/readme/repository_showcase_render.png differ diff --git a/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java b/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java index 1659a2b13..fe1c47a6f 100644 --- a/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java +++ b/examples/src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java @@ -9,6 +9,7 @@ import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.examples.support.theme.BusinessTheme; import com.demcha.compose.font.FontName; @@ -176,7 +177,8 @@ private static void barcodeCard(com.demcha.compose.document.dsl.SectionBuilder s .addParagraph(p -> p .text(title) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(INK) .build()) diff --git a/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java b/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java index 2cd7fe162..89dbec34d 100644 --- a/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java +++ b/examples/src/main/java/com/demcha/examples/features/canvas/CanvasLayerExample.java @@ -49,13 +49,13 @@ public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("features/canvas", "canvas-layer-showcase.pdf"); DocumentTextStyle eyebrow = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(10) .color(ACCENT) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle headline = DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) .size(34) .color(INK) .decoration(DocumentTextDecoration.BOLD) @@ -66,7 +66,8 @@ public static Path generate() throws Exception { .color(MUTED) .build(); DocumentTextStyle name = DocumentTextStyle.builder() - .fontName(FontName.TIMES_ITALIC) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.ITALIC) .size(24) .color(INK) .build(); @@ -76,7 +77,7 @@ public static Path generate() throws Exception { .color(INK) .build(); DocumentTextStyle sealLabel = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(9) .color(ACCENT) .decoration(DocumentTextDecoration.BOLD) @@ -87,13 +88,14 @@ public static Path generate() throws Exception { .color(MUTED) .build(); DocumentTextStyle title = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(15) .color(INK) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle caption = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_OBLIQUE) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.ITALIC) .size(10) .color(MUTED) .build(); diff --git a/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java b/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java index 268709602..4844ee55d 100644 --- a/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java +++ b/examples/src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java @@ -12,6 +12,7 @@ import com.demcha.compose.document.output.DocumentWatermarkPosition; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.examples.support.theme.BusinessTheme; import com.demcha.compose.font.FontName; @@ -253,7 +254,8 @@ public static void main(String[] args) throws Exception { private static DocumentTextStyle panelHeadline() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(INK) .build(); diff --git a/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java b/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java index 86e9ff5cb..3413d57f3 100644 --- a/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java +++ b/examples/src/main/java/com/demcha/examples/features/docx/WordExportExample.java @@ -59,13 +59,13 @@ public static Path generate() throws Exception { Path docxFile = ExampleOutputPaths.prepare("features/docx", "word-export-companion.docx"); DocumentTextStyle title = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(20) .color(NAVY) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle heading = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(13) .color(INK) .decoration(DocumentTextDecoration.BOLD) @@ -76,7 +76,8 @@ public static Path generate() throws Exception { .color(INK) .build(); DocumentTextStyle caption = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_OBLIQUE) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.ITALIC) .size(10) .color(MUTED) .build(); @@ -170,7 +171,8 @@ public static Path generate() throws Exception { .center(new com.demcha.compose.document.dsl.ParagraphBuilder() .text("GC") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(20) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(20) .color(DocumentColor.WHITE).build()) .align(com.demcha.compose.document.node.TextAlign.CENTER) .build())) diff --git a/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java b/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java index 3a8c05a52..e492f7a9e 100644 --- a/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java +++ b/examples/src/main/java/com/demcha/examples/features/lists/NestedListExample.java @@ -35,19 +35,20 @@ public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("features/lists", "nested-list-showcase.pdf"); DocumentTextStyle title = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(20) .color(INK) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle sectionHeading = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(13) .color(INK) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle caption = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_OBLIQUE) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.ITALIC) .size(10) .color(MUTED) .build(); diff --git a/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java b/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java index d9fd8038c..0724e9589 100644 --- a/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java +++ b/examples/src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java @@ -70,9 +70,9 @@ public static Path generate() throws Exception { .name("InitialCircle") .padding(12) .stroke(DocumentStroke.of(GOLD, 1.4)) - .center(label("GC", style(FontName.HELVETICA_BOLD, 24, + .center(label("GC", style(FontName.HELVETICA, 24, DocumentTextDecoration.BOLD, DocumentColor.WHITE))) - .position(label("CLIP", style(FontName.HELVETICA_BOLD, 8, + .position(label("CLIP", style(FontName.HELVETICA, 8, DocumentTextDecoration.BOLD, SOFT_GOLD)), 0, -12, LayerAlign.BOTTOM_CENTER)) .addParagraph(paragraph -> paragraph @@ -86,10 +86,10 @@ public static Path generate() throws Exception { .name("OffsetEllipse") .padding(10) .stroke(DocumentStroke.of(DEEP_TEAL, 1.0)) - .center(label("Overlay", style(FontName.HELVETICA_BOLD, 15, + .center(label("Overlay", style(FontName.HELVETICA, 15, DocumentTextDecoration.BOLD, INK))) .topRight(pill(44, 18, DEEP_TEAL)) - .position(label("TOP", style(FontName.HELVETICA_BOLD, 7, + .position(label("TOP", style(FontName.HELVETICA, 7, DocumentTextDecoration.BOLD, DocumentColor.WHITE)), -6, 5, LayerAlign.TOP_RIGHT)) .addParagraph(paragraph -> paragraph @@ -108,7 +108,7 @@ public static Path generate() throws Exception { .padding(12) .center(cardCopy()) .topRight(pill(46, 18, DEEP_TEAL)) - .position(label("NEW", style(FontName.HELVETICA_BOLD, 7, + .position(label("NEW", style(FontName.HELVETICA, 7, DocumentTextDecoration.BOLD, DocumentColor.WHITE)), -6, 5, LayerAlign.TOP_RIGHT)) .addParagraph(paragraph -> paragraph @@ -217,7 +217,7 @@ private static DocumentNode seatGuide() { private static DocumentNode valignLabel(String text, TextVerticalAlign verticalAlign) { return new ParagraphBuilder() .text(text) - .textStyle(style(FontName.HELVETICA_BOLD, 34, DocumentTextDecoration.BOLD, DocumentColor.WHITE)) + .textStyle(style(FontName.HELVETICA, 34, DocumentTextDecoration.BOLD, DocumentColor.WHITE)) .align(TextAlign.CENTER) .verticalAlign(verticalAlign) .margin(DocumentInsets.zero()) @@ -240,7 +240,7 @@ private static void cornerCard(SectionBuilder col, String caption, DocumentCorne .roundedRect(96, 56, corners) .fillColor(DEEP_TEAL) .clipPolicy(ClipPolicy.CLIP_PATH) - .center(label("Aa", style(FontName.HELVETICA_BOLD, 16, + .center(label("Aa", style(FontName.HELVETICA, 16, DocumentTextDecoration.BOLD, DocumentColor.WHITE)))) .addParagraph(paragraph -> paragraph .text(caption) diff --git a/examples/src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java b/examples/src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java index 1b745ba3f..de4862891 100644 --- a/examples/src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java +++ b/examples/src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java @@ -84,7 +84,7 @@ public final class SvgIconGalleryExample { private static final DocumentColor MUTED = DocumentColor.rgb(90, 96, 105); private static final DocumentTextStyle LABEL_STYLE = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(6.4) .decoration(DocumentTextDecoration.BOLD) .color(LABEL_INK) diff --git a/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java b/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java index 56dae6fb7..2a95be6ff 100644 --- a/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java +++ b/examples/src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java @@ -53,19 +53,20 @@ public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("features/tables", "composed-table-cell-showcase.pdf"); DocumentTextStyle title = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(20) .color(INK) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle sectionHeading = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(13) .color(INK) .decoration(DocumentTextDecoration.BOLD) .build(); DocumentTextStyle caption = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_OBLIQUE) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.ITALIC) .size(10) .color(MUTED) .build(); @@ -75,7 +76,7 @@ public static Path generate() throws Exception { .color(INK) .build(); DocumentTextStyle headerText = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .size(11) .color(DocumentColor.WHITE) .decoration(DocumentTextDecoration.BOLD) diff --git a/examples/src/main/java/com/demcha/examples/features/tables/InlineCodeColumnWrapExample.java b/examples/src/main/java/com/demcha/examples/features/tables/InlineCodeColumnWrapExample.java index 37df23ee9..ed09606ff 100644 --- a/examples/src/main/java/com/demcha/examples/features/tables/InlineCodeColumnWrapExample.java +++ b/examples/src/main/java/com/demcha/examples/features/tables/InlineCodeColumnWrapExample.java @@ -51,14 +51,15 @@ public static Path generate() throws Exception { Path outputFile = ExampleOutputPaths.prepare("features/tables", "inline-code-column-wrap.pdf"); DocumentTextStyle title = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(20).color(INK) + .fontName(FontName.HELVETICA).size(20).color(INK) .decoration(DocumentTextDecoration.BOLD).build(); DocumentTextStyle caption = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_OBLIQUE).size(10).color(MUTED).build(); + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.ITALIC).size(10).color(MUTED).build(); DocumentTextStyle body = DocumentTextStyle.builder() .fontName(FontName.HELVETICA).size(10.5).color(INK).build(); DocumentTextStyle headerText = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(11).color(DocumentColor.WHITE) + .fontName(FontName.HELVETICA).size(11).color(DocumentColor.WHITE) .decoration(DocumentTextDecoration.BOLD).build(); DocumentTableStyle headerStyle = DocumentTableStyle.builder() diff --git a/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java b/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java index 4e7a5bae6..823a1c30e 100644 --- a/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java +++ b/examples/src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java @@ -59,7 +59,7 @@ public static Path generate() throws Exception { .stroke(DocumentStroke.of(RULE, 0.6)) .padding(DocumentInsets.of(8)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .decoration(DocumentTextDecoration.BOLD) .color(DocumentColor.WHITE) .build()) @@ -69,7 +69,7 @@ public static Path generate() throws Exception { .stroke(DocumentStroke.of(RULE, 0.6)) .padding(DocumentInsets.of(8)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .decoration(DocumentTextDecoration.BOLD) .color(INK) .build()) diff --git a/examples/src/main/java/com/demcha/examples/features/text/EmojiClipPathReportExample.java b/examples/src/main/java/com/demcha/examples/features/text/EmojiClipPathReportExample.java index 77e01969f..4cd751cf8 100644 --- a/examples/src/main/java/com/demcha/examples/features/text/EmojiClipPathReportExample.java +++ b/examples/src/main/java/com/demcha/examples/features/text/EmojiClipPathReportExample.java @@ -90,7 +90,7 @@ public static Path generate() throws Exception { .fillColor(HEADER_FILL) .stroke(DocumentStroke.of(RULE, 0.5)) .padding(DocumentInsets.of(6)) - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA) .decoration(DocumentTextDecoration.BOLD).color(DocumentColor.WHITE).build()) .build(); @@ -103,7 +103,7 @@ public static Path generate() throws Exception { .spacing(8) .addParagraph(p -> p .text("Emoji using SVG clip-path (" + entries.size() + " glyphs)") - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(14).color(INK).build()) .margin(DocumentInsets.zero())) .addParagraph(p -> p diff --git a/examples/src/main/java/com/demcha/examples/features/text/EmojiGalleryExample.java b/examples/src/main/java/com/demcha/examples/features/text/EmojiGalleryExample.java index e87b7e9be..0e0cdf7b8 100644 --- a/examples/src/main/java/com/demcha/examples/features/text/EmojiGalleryExample.java +++ b/examples/src/main/java/com/demcha/examples/features/text/EmojiGalleryExample.java @@ -5,6 +5,7 @@ import com.demcha.compose.document.api.DocumentSession; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.font.FontName; @@ -45,7 +46,7 @@ public static Path generate() throws Exception { var flow = document.pageFlow().name("EmojiGallery").spacing(6); flow.addParagraph(p -> p .text("Noto Emoji — full set (" + glyphs.size() + " glyphs)") - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(15).color(INK).build()) .margin(DocumentInsets.zero())); flow.addParagraph(p -> p diff --git a/examples/src/main/java/com/demcha/examples/features/text/EmojiShortcodeExample.java b/examples/src/main/java/com/demcha/examples/features/text/EmojiShortcodeExample.java index 7b85ecc73..5634d1f14 100644 --- a/examples/src/main/java/com/demcha/examples/features/text/EmojiShortcodeExample.java +++ b/examples/src/main/java/com/demcha/examples/features/text/EmojiShortcodeExample.java @@ -7,6 +7,7 @@ import com.demcha.compose.document.dsl.SectionBuilder; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.examples.support.theme.BusinessTheme; import com.demcha.compose.font.FontName; @@ -142,7 +143,8 @@ private static void labelledRow(SectionBuilder section, String label, Consumer section .addParagraph(p -> p.text("Emoji rendering — SVG (vector) vs PNG (raster)") - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(16).color(INK).build()) .margin(DocumentInsets.zero())) .addParagraph(p -> p.text("Both columns draw the same glyph at " + (int) GLYPH_PT diff --git a/examples/src/main/java/com/demcha/examples/features/text/InlineHighlightExample.java b/examples/src/main/java/com/demcha/examples/features/text/InlineHighlightExample.java index c5f7ad1ba..709a582e2 100644 --- a/examples/src/main/java/com/demcha/examples/features/text/InlineHighlightExample.java +++ b/examples/src/main/java/com/demcha/examples/features/text/InlineHighlightExample.java @@ -7,6 +7,7 @@ import com.demcha.compose.document.dsl.SectionBuilder; import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.examples.support.theme.BusinessTheme; import com.demcha.compose.font.FontName; @@ -138,7 +139,8 @@ private static void labelledRow(SectionBuilder section, String label, Consumer rich .plain("Project ") .style("GraphCompose", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(BRAND) .build()) .plain(" — code: ") .style("RT-2026-04", DocumentTextStyle.builder() - .fontName(FontName.COURIER_BOLD) + .fontName(FontName.COURIER) + .decoration(DocumentTextDecoration.BOLD) .size(10) .color(INK) .build()))) @@ -174,7 +177,8 @@ private static void labelledRow(com.demcha.compose.document.dsl.SectionBuilder s .addParagraph(p -> p .text(label) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8.5) .color(MUTED) .build()) diff --git a/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java b/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java index 48319f293..c78c74b5a 100644 --- a/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java +++ b/examples/src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java @@ -7,6 +7,7 @@ import com.demcha.compose.document.style.DocumentCornerRadius; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.examples.support.theme.BusinessTheme; import com.demcha.compose.font.FontName; @@ -181,7 +182,8 @@ public static void main(String[] args) throws Exception { private static DocumentTextStyle panelHeadline() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(INK) .build(); @@ -189,7 +191,8 @@ private static DocumentTextStyle panelHeadline() { private static DocumentTextStyle headlineOnDark() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(DocumentColor.WHITE) .build(); diff --git a/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java b/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java index b73eb591d..db8cb0c1b 100644 --- a/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java +++ b/examples/src/main/java/com/demcha/examples/features/transforms/TransformsExample.java @@ -78,7 +78,7 @@ public static Path generate() throws Exception { .padding(10) .stroke(DocumentStroke.of(GOLD, 1.4)) .rotate(15) - .center(label("M&A", style(FontName.HELVETICA_BOLD, 22, + .center(label("M&A", style(FontName.HELVETICA, 22, DocumentTextDecoration.BOLD, DocumentColor.WHITE))))) .addSection("RotateSlot2", col -> col .addCircle(110, DEEP_TEAL, circle -> circle @@ -86,14 +86,14 @@ public static Path generate() throws Exception { .padding(10) .stroke(DocumentStroke.of(GOLD, 1.4)) .rotate(-15) - .center(label("M&A", style(FontName.HELVETICA_BOLD, 22, + .center(label("M&A", style(FontName.HELVETICA, 22, DocumentTextDecoration.BOLD, DocumentColor.WHITE))))) .addSection("RotateSlot3", col -> col .addCircle(110, DEEP_TEAL, circle -> circle .name("PlainSeal") .padding(10) .stroke(DocumentStroke.of(GOLD, 1.4)) - .center(label("M&A", style(FontName.HELVETICA_BOLD, 22, + .center(label("M&A", style(FontName.HELVETICA, 22, DocumentTextDecoration.BOLD, DocumentColor.WHITE)))))) .addParagraph(p -> p .text("Tilt left, tilt right, no tilt — outline plus label rotate together.") @@ -117,7 +117,7 @@ public static Path generate() throws Exception { .stroke(DocumentStroke.of(DEEP_TEAL, 0.8)) .padding(12) .scale(0.7) - .center(label("scale(0.7)", style(FontName.HELVETICA_BOLD, 14, + .center(label("scale(0.7)", style(FontName.HELVETICA, 14, DocumentTextDecoration.BOLD, INK))))) .addSection("ScaleSlot2", col -> col .addContainer(card -> card @@ -127,7 +127,7 @@ public static Path generate() throws Exception { .stroke(DocumentStroke.of(DEEP_TEAL, 0.8)) .padding(12) .scale(1.1, 0.85) - .center(label("1.1 x 0.85", style(FontName.HELVETICA_BOLD, 14, + .center(label("1.1 x 0.85", style(FontName.HELVETICA, 14, DocumentTextDecoration.BOLD, INK))))) .addSection("ScaleSlot3", col -> col .addContainer(card -> card @@ -136,7 +136,7 @@ public static Path generate() throws Exception { .fillColor(DocumentColor.WHITE) .stroke(DocumentStroke.of(DEEP_TEAL, 0.8)) .padding(12) - .center(label("1.0", style(FontName.HELVETICA_BOLD, 14, + .center(label("1.0", style(FontName.HELVETICA, 14, DocumentTextDecoration.BOLD, INK)))))) .addParagraph(p -> p .text("Scaling pivots around the outline centre and composes with rotate().") diff --git a/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java b/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java index f801d8d18..2dd9cfd4e 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/BusinessReportExample.java @@ -12,6 +12,7 @@ import com.demcha.compose.document.table.DocumentTableColumn; import com.demcha.compose.document.table.DocumentTableStyle; import com.demcha.examples.support.theme.BusinessTheme; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.font.FontName; import com.demcha.examples.support.ExampleOutputPaths; @@ -229,13 +230,15 @@ static void compose(DocumentSession document) { .margin(DocumentInsets.zero())) .addRich(rich -> rich .style("Revenue", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(NAVY) .build()) .style(" ", legendLabel()) .style("Profit", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(GOLD) .build())) @@ -293,7 +296,8 @@ private static void buildMetricsTable(com.demcha.compose.document.dsl.TableBuild .headerStyle(DocumentTableStyle.builder() .padding(new DocumentInsets(8, 8, 8, 8)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(DocumentColor.WHITE) .build()) @@ -331,7 +335,8 @@ private static void kpiCard(SectionBuilder section, .center(new ParagraphBuilder() .text(iconGlyph) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(13) .color(NAVY) .build()) @@ -341,7 +346,8 @@ private static void kpiCard(SectionBuilder section, .addParagraph(p -> p .text(value) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(24) .color(INK) .build()) @@ -349,7 +355,8 @@ private static void kpiCard(SectionBuilder section, .addParagraph(p -> p .text(label) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9.5) .color(MUTED) .build()) @@ -407,13 +414,15 @@ private static void chartBlock(SectionBuilder section) { .margin(DocumentInsets.zero())) .addRich(rich -> rich .style("Revenue", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(NAVY) .build()) .style(" ", legendLabel()) .style("Profit", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(GOLD) .build())) @@ -513,7 +522,8 @@ private static java.util.List hero private static DocumentTextStyle bandLeft() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8) .color(MUTED) .build(); @@ -521,7 +531,8 @@ private static DocumentTextStyle bandLeft() { private static DocumentTextStyle bandRight() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8) .color(MUTED) .build(); @@ -529,7 +540,8 @@ private static DocumentTextStyle bandRight() { private static DocumentTextStyle heroTitle() { return DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(24) .color(INK) .build(); @@ -545,7 +557,8 @@ private static DocumentTextStyle heroBody() { private static DocumentTextStyle sectionTitle() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(12) .color(INK) .build(); diff --git a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckCharts.java b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckCharts.java index 74558cc34..7c3b4bada 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckCharts.java +++ b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckCharts.java @@ -25,6 +25,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.table.DocumentTableColumn; import com.demcha.compose.document.table.DocumentTableStyle; @@ -65,7 +66,8 @@ static void benchTable(EngineDeckData.BenchRun b, TableBuilder table) { .headerStyle(DocumentTableStyle.builder() .padding(DocumentInsets.symmetric(8, 10)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(9).color(DocumentColor.WHITE).build()) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(9).color(DocumentColor.WHITE).build()) .fillColor(VIOLET_DEEP) .stroke(DocumentStroke.of(VIOLET_DEEP, 0.5)) .build()) diff --git a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java index a9872a742..87ab4662d 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckExample.java @@ -43,6 +43,7 @@ import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.font.FontName; @@ -418,12 +419,12 @@ private static DocumentNode versionBlock() { DocumentNode pill = new ShapeContainerBuilder().name("VerPill") .roundedRect(96, 30, 8).fillColor(VIOLET_DEEP) .center(new ParagraphBuilder().text("v" + pillVersion) - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(14).color(DocumentColor.WHITE).build()) .align(TextAlign.CENTER).margin(DocumentInsets.zero()).build()) .build(); DocumentNode codename = new ParagraphBuilder().text(CODENAME) - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_OBLIQUE) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.ITALIC) .size(14).color(GREEN).build()) .margin(DocumentInsets.zero()).build(); return new ShapeContainerBuilder().name("VersionBlock") @@ -436,7 +437,7 @@ private static DocumentNode versionBlock() { /** Column header: a violet step number inline with the title. */ private static void colHeader(SectionBuilder s, String number, String title) { s.addRich(r -> r - .style(number, DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .style(number, DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(16).color(VIOLET).build()) .style(" " + title, colTitle())); } @@ -444,7 +445,7 @@ private static void colHeader(SectionBuilder s, String number, String title) { private static void arrowCell(SectionBuilder s) { s.padding(DocumentInsets.zero()) .addParagraph(p -> p.text(">") - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(26).color(VIOLET).build()) .align(TextAlign.CENTER).margin(DocumentInsets.top(96))); } @@ -530,7 +531,7 @@ private static DocumentNode backendCard(String iconName, String title, String su .position(new ParagraphBuilder().text(title).textStyle(backendTitle()) .margin(DocumentInsets.zero()).build(), 37, -7, LayerAlign.CENTER_LEFT) .position(new ParagraphBuilder().text(sub) - .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD) + .textStyle(DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD) .size(7.5).color(accent).build()) .margin(DocumentInsets.zero()).build(), 37, 8, LayerAlign.CENTER_LEFT) .build(); @@ -596,7 +597,7 @@ private static SvgIcon lightIcon(String name) { // ── Banner text styles ─────────────────────────────────────────────────── private static DocumentTextStyle colTitle() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(13).color(ON_DARK).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(13).color(ON_DARK).build(); } private static DocumentTextStyle colSub() { @@ -604,15 +605,15 @@ private static DocumentTextStyle colSub() { } private static DocumentTextStyle gridLabel() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(8.5).color(ON_DARK).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(8.5).color(ON_DARK).build(); } private static DocumentTextStyle backendTitle() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(9.5).color(ON_DARK).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(9.5).color(ON_DARK).build(); } private static DocumentTextStyle chipLabelLight() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(9.5).color(ON_DARK).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(9.5).color(ON_DARK).build(); } private static DocumentTextStyle itemDesc() { @@ -663,7 +664,8 @@ private static void pipelineStep(com.demcha.compose.document.dsl.RowBuilder row, private static void pipeArrow(com.demcha.compose.document.dsl.RowBuilder row) { row.addSection(s -> s.padding(DocumentInsets.zero()) .addParagraph(p -> p.text(">").textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(20).color(VIOLET).build()) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(20).color(VIOLET).build()) .align(TextAlign.CENTER).margin(DocumentInsets.top(46)))); } @@ -700,10 +702,12 @@ private static void kicker(SectionBuilder section, String kicker, String heading .padding(DocumentInsets.zero()) .spacing(7) .addParagraph(p -> p.text(kicker).textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(9).color(VIOLET_DEEP).build()) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(9).color(VIOLET_DEEP).build()) .margin(DocumentInsets.zero())) .addParagraph(p -> p.text(heading).textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(18).color(INK).build()) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(18).color(INK).build()) .margin(DocumentInsets.zero())); } @@ -712,7 +716,7 @@ private static DocumentTextStyle tagline() { } private static DocumentTextStyle h3() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(11).color(INK).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(11).color(INK).build(); } private static DocumentTextStyle body() { @@ -724,11 +728,11 @@ private static DocumentTextStyle caption() { } private static DocumentTextStyle chipLabel() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(9.5).color(INK).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(9.5).color(INK).build(); } private static DocumentTextStyle stepNum() { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(16).color(VIOLET).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(16).color(VIOLET).build(); } // ───────────────────────────────────────────────────────────────────────── diff --git a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckV2Example.java b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckV2Example.java index 9f8e7ac65..fadedc3f0 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/EngineDeckV2Example.java +++ b/examples/src/main/java/com/demcha/examples/flagships/EngineDeckV2Example.java @@ -33,6 +33,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.font.FontName; @@ -916,7 +917,8 @@ private static SvgIcon icon(String name) { private static DocumentTextStyle display(double size, DocumentColor color) { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(size) .color(color) .build(); @@ -956,7 +958,8 @@ private static DocumentTextStyle body(double size, DocumentColor color) { private static DocumentTextStyle body(double size, DocumentColor color, boolean bold) { DocumentTextStyle.Builder builder = DocumentTextStyle.builder() - .fontName(bold ? FontName.HELVETICA_BOLD : FontName.HELVETICA) + .fontName(FontName.HELVETICA) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) .size(size) .color(color); return builder.build(); @@ -964,7 +967,8 @@ private static DocumentTextStyle body(double size, DocumentColor color, boolean private static DocumentTextStyle mono(double size, DocumentColor color, boolean bold) { DocumentTextStyle.Builder builder = DocumentTextStyle.builder() - .fontName(bold ? FontName.COURIER_BOLD : FontName.COURIER) + .fontName(FontName.COURIER) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) .size(size) .color(color); return builder.build(); diff --git a/examples/src/main/java/com/demcha/examples/flagships/EngineShowcase.java b/examples/src/main/java/com/demcha/examples/flagships/EngineShowcase.java index d551e03d8..250d0e558 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/EngineShowcase.java +++ b/examples/src/main/java/com/demcha/examples/flagships/EngineShowcase.java @@ -11,6 +11,7 @@ import com.demcha.compose.document.style.DocumentCornerRadius; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.font.FontName; import com.demcha.examples.support.ExampleOutputPaths; @@ -258,7 +259,8 @@ private static void capabilityColumn(com.demcha.compose.document.dsl.SectionBuil private static DocumentTextStyle bandLeft() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(10) .color(NAVY) .build(); @@ -274,7 +276,8 @@ private static DocumentTextStyle bandRight() { private static DocumentTextStyle headline() { return DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(38) .color(INK) .build(); @@ -282,7 +285,8 @@ private static DocumentTextStyle headline() { private static DocumentTextStyle headlineAccent() { return DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD_ITALIC) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD_ITALIC) .size(38) .color(ACCENT) .build(); @@ -298,7 +302,8 @@ private static DocumentTextStyle tagline() { private static DocumentTextStyle kpiValue() { return DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(26) .color(NAVY) .build(); @@ -306,7 +311,8 @@ private static DocumentTextStyle kpiValue() { private static DocumentTextStyle kpiLabel() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(ACCENT) .build(); @@ -322,7 +328,8 @@ private static DocumentTextStyle kpiDetail() { private static DocumentTextStyle capLabel() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9) .color(NAVY) .build(); diff --git a/examples/src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java b/examples/src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java index 0db33bf52..a98f8b99b 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java @@ -25,6 +25,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.style.DocumentTransform; import com.demcha.compose.document.style.ShapePoint; @@ -199,7 +200,8 @@ public static Path generate() throws Exception { .zebra(DocumentColor.WHITE, DocumentColor.rgb(248, 246, 240)) .totalRow(com.demcha.compose.document.table.DocumentTableStyle.builder() .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(9.5).color(INK).build()) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(9.5).color(INK).build()) .fillColor(DocumentColor.rgb(240, 237, 228)) .padding(DocumentInsets.of(6)).build(), "Total", "60", "66"))); @@ -385,7 +387,8 @@ public static Path generate() throws Exception { .center(new com.demcha.compose.document.dsl.ParagraphBuilder() .text("GC") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(20) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(20) .color(DocumentColor.WHITE).build()) .align(com.demcha.compose.document.node.TextAlign.CENTER) .build()))); @@ -475,7 +478,8 @@ private static void feature(com.demcha.compose.document.dsl.PageFlowBuilder flow .addParagraph(p -> p .text(title) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(11).color(INK).build()) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(11).color(INK).build()) .bookmark(new DocumentBookmarkOptions(title, 1)) .margin(DocumentInsets.zero())) .addSection("Code", c -> c @@ -540,7 +544,8 @@ private static com.demcha.compose.document.node.DocumentNode iconCard(String nam .center(new com.demcha.compose.document.dsl.ParagraphBuilder() .text(label) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(6.2) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(6.2) .color(DocumentColor.rgb(82, 90, 102)).build()) .align(com.demcha.compose.document.node.TextAlign.CENTER) .build()) @@ -555,7 +560,8 @@ private static com.demcha.compose.document.node.DocumentNode badge(String text) .center(new com.demcha.compose.document.dsl.ParagraphBuilder() .text(text) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD).size(9) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD).size(9) .color(DocumentColor.WHITE).build()) .align(com.demcha.compose.document.node.TextAlign.CENTER) .build()) diff --git a/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java b/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java index f2a0b45e2..8b16abd59 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/FinancialReportExample.java @@ -30,6 +30,7 @@ import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentPathSegment; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.font.FontName; import com.demcha.examples.support.ExampleOutputPaths; @@ -268,7 +269,8 @@ private static void gauge(SectionBuilder section, double percent, String label) ChartStyle style = ChartStyle.builder() .palette(DocumentPaint.solid(ORANGE), DocumentPaint.solid(GAUGE_TRACK)) .donutCenterTextStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(22) .color(INK) .build()) @@ -496,7 +498,8 @@ private static ChartStyle tealBars() { private static DocumentTextStyle titleStyle() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(19) .color(INK) .build(); @@ -512,7 +515,8 @@ private static DocumentTextStyle subtitleStyle() { private static DocumentTextStyle periodStyle() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(15) .color(INK) .build(); @@ -520,7 +524,8 @@ private static DocumentTextStyle periodStyle() { private static DocumentTextStyle bandTitleStyle() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(12.5) .color(TEAL_DARK) .build(); @@ -528,7 +533,8 @@ private static DocumentTextStyle bandTitleStyle() { private static DocumentTextStyle cardTitleStyle() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(TEAL_DARK) .build(); @@ -552,7 +558,8 @@ private static DocumentTextStyle gaugeLabelStyle() { private static DocumentTextStyle kpiTokenStyle(DocumentColor color, double size) { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(size) .color(color) .build(); @@ -560,7 +567,8 @@ private static DocumentTextStyle kpiTokenStyle(DocumentColor color, double size) private static DocumentTextStyle kpiValueStyle() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(INK) .build(); diff --git a/examples/src/main/java/com/demcha/examples/flagships/LinkedInCarouselExample.java b/examples/src/main/java/com/demcha/examples/flagships/LinkedInCarouselExample.java index 219a6c410..e5033bb28 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/LinkedInCarouselExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/LinkedInCarouselExample.java @@ -20,6 +20,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.font.FontName; import com.demcha.examples.support.ExampleOutputPaths; @@ -410,7 +411,8 @@ private static void point(SectionBuilder slide, String title, DocumentColor acce card.addParagraph(p -> p .text(title) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(40 * SCALE) .color(ON_DARK) .build()) @@ -432,7 +434,8 @@ private static void formatChip(RowBuilder row, String name, String lane, card.addParagraph(p -> p .text(name) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(56 * SCALE) .color(accent) .build()) @@ -459,7 +462,8 @@ private static void ratio(RowBuilder row, String value, String label, DocumentCo card.addParagraph(p -> p .text(value) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(64 * SCALE) .color(accent) .build()) @@ -517,7 +521,8 @@ private static String snapshotDate(EngineDeckData.BenchRun bench) { private static DocumentTextStyle headline(double size) { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(size * SCALE) .color(ON_DARK) .build(); diff --git a/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java b/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java index cecb42c41..fc9c91348 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java @@ -199,11 +199,11 @@ static void compose(DocumentSession document) { .rotate(-12) .clipPolicy(ClipPolicy.CLIP_PATH) .center(label("APPROVED", - style(FontName.HELVETICA_BOLD, 13, + style(FontName.HELVETICA, 13, DocumentTextDecoration.BOLD, DocumentColor.WHITE))) .position(label("SAMPLE", - style(FontName.HELVETICA_BOLD, 7.5, + style(FontName.HELVETICA, 7.5, DocumentTextDecoration.BOLD, SOFT_GOLD)), 0, -16, LayerAlign.BOTTOM_CENTER))) @@ -283,7 +283,8 @@ static void compose(DocumentSession document) { .headerStyle(DocumentTableStyle.builder() .padding(new DocumentInsets(8, 9, 8, 9)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9.5) .color(DocumentColor.WHITE) .build()) @@ -304,7 +305,8 @@ static void compose(DocumentSession document) { table.totalRow(DocumentTableStyle.builder() .padding(new DocumentInsets(8, 9, 8, 9)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(10) .color(DocumentColor.WHITE) .build()) @@ -437,7 +439,8 @@ private static String speed(String scenario) { private static DocumentTextStyle label() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8.5) .color(MUTED) .build(); @@ -445,7 +448,8 @@ private static DocumentTextStyle label() { private static DocumentTextStyle panelHeadline() { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11) .color(INK) .build(); diff --git a/examples/src/main/java/com/demcha/examples/flagships/MavenBannerPptxExample.java b/examples/src/main/java/com/demcha/examples/flagships/MavenBannerPptxExample.java index dfb684132..fbb3b8067 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/MavenBannerPptxExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/MavenBannerPptxExample.java @@ -20,6 +20,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.font.FontName; @@ -488,7 +489,7 @@ private static DocumentTextStyle sans(double size, DocumentColor color) { } private static DocumentTextStyle sansBold(double size, DocumentColor color) { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(size).color(color).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(size).color(color).build(); } private static DocumentTextStyle mono(double size, DocumentColor color) { @@ -496,7 +497,7 @@ private static DocumentTextStyle mono(double size, DocumentColor color) { } private static DocumentTextStyle monoBold(double size, DocumentColor color) { - return DocumentTextStyle.builder().fontName(FontName.COURIER_BOLD).size(size).color(color).build(); + return DocumentTextStyle.builder().fontName(FontName.COURIER).decoration(DocumentTextDecoration.BOLD).size(size).color(color).build(); } /** diff --git a/examples/src/main/java/com/demcha/examples/flagships/TwinOutputExample.java b/examples/src/main/java/com/demcha/examples/flagships/TwinOutputExample.java index f5d085d31..05e700a08 100644 --- a/examples/src/main/java/com/demcha/examples/flagships/TwinOutputExample.java +++ b/examples/src/main/java/com/demcha/examples/flagships/TwinOutputExample.java @@ -18,6 +18,7 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentPaint; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.svg.SvgIcon; import com.demcha.compose.font.FontName; @@ -376,7 +377,7 @@ private static SvgIcon icon(String name) { } private static DocumentTextStyle display(double size, DocumentColor color) { - return DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(size).color(color).build(); + return DocumentTextStyle.builder().fontName(FontName.HELVETICA).decoration(DocumentTextDecoration.BOLD).size(size).color(color).build(); } private static DocumentTextStyle body(double size, DocumentColor color) { @@ -385,7 +386,8 @@ private static DocumentTextStyle body(double size, DocumentColor color) { private static DocumentTextStyle mono(double size, DocumentColor color, boolean bold) { return DocumentTextStyle.builder() - .fontName(bold ? FontName.COURIER_BOLD : FontName.COURIER) + .fontName(FontName.COURIER) + .decoration(bold ? DocumentTextDecoration.BOLD : DocumentTextDecoration.DEFAULT) .size(size) .color(color) .build(); diff --git a/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java b/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java index 67184dd8c..9ed1dce41 100644 --- a/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java +++ b/examples/src/main/java/com/demcha/examples/support/WeeklyScheduleRenderer.java @@ -8,6 +8,7 @@ import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.compose.document.table.DocumentTableCell; import com.demcha.compose.document.table.DocumentTableColumn; @@ -325,11 +326,19 @@ public record Layout( /** Default landscape layout matching the AURORA demo board. */ public static Layout landscape() { + // The name column and the seven day columns are sized to fill the printable + // width rather than picked: the page is 841.88977 wide, the side margins take + // 8 each, and the columns divide what is left. The board is printed, so + // leftover width is wasted paper — the previous 90 + 7 x 105 left 0.89pt of + // it. The day columns also have to be wide enough for a bold day note, which + // spans four fixed sub-columns and so cannot borrow width from a neighbour. + double printable = PAGE_WIDTH - 2 * SIDE_MARGIN; + double dayColumn = 105.5; return new Layout( - DocumentPageSize.of(841.88977, 472), - new DocumentInsets(14, 8, 14, 8), - 90, - 105); + DocumentPageSize.of(PAGE_WIDTH, 472), + new DocumentInsets(14, SIDE_MARGIN, 14, SIDE_MARGIN), + Math.floor((printable - DAYS_IN_WEEK * dayColumn) * 100) / 100, + dayColumn); } public double subColWidth() { return dayColWidth / 4.0; } @@ -339,6 +348,12 @@ public static Layout landscape() { // ───────────────────────── Constants ───────────────────────────── private static final int DAYS_IN_WEEK = 7; + + /** Landscape A4, the sheet the board is printed on. */ + private static final double PAGE_WIDTH = 841.88977; + + /** Left and right margin; the columns divide what is left. */ + private static final double SIDE_MARGIN = 8; private static final DayShift[] EMPTY_WEEK = { DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE, DayShift.NONE @@ -509,7 +524,8 @@ public static void renderTo(Path outputFile, .text("S") .align(TextAlign.CENTER) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(13) .color(theme.brandAccent()) .build()) @@ -717,7 +733,8 @@ private static void chip(SectionBuilder section, String label, DocumentColor fil .text(label) .align(TextAlign.CENTER) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8.5) .color(DocumentColor.WHITE) .build()) @@ -730,7 +747,8 @@ private static DocumentTableStyle brandCellStyle(Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(8, 4, 8, 4)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(18) .color(theme.brandAccent()) .build()) @@ -744,7 +762,8 @@ private static DocumentTableStyle dayNameCellStyle(Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(6, 4, 4, 4)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.TIMES_BOLD) + .fontName(FontName.TIMES_ROMAN) + .decoration(DocumentTextDecoration.BOLD) .size(11.5) .color(theme.ink()) .build()) @@ -758,8 +777,11 @@ private static DocumentTableStyle dayNoteCellStyle(Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(2, 5, 6, 5)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(5.9) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) + // 5.9 bold overflows the four fixed sub-columns a note spans, + // and a spanned cell cannot borrow width from fixed neighbours. + .size(5.75) .color(theme.ink()) .build()) .stroke(DocumentStroke.of(theme.grid(), 0.3)) @@ -773,7 +795,8 @@ private static DocumentTableStyle statusFill(DocumentColor fill, Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(8.5, 4, 8.5, 4)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8.5) .color(textOn(fill, theme)) .build()) @@ -814,7 +837,8 @@ private static DocumentTableStyle nameCellStyle(Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(7, 6, 7, 6)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(9.1) .color(theme.brandDark()) .build()) @@ -828,7 +852,8 @@ private static DocumentTableStyle coversLabelStyle(Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(5, 6, 5, 6)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8.8) .color(DocumentColor.WHITE) .build()) @@ -843,7 +868,8 @@ private static DocumentTableStyle coversCellStyle(Theme theme) { return DocumentTableStyle.builder() .padding(new DocumentInsets(5, 4, 5, 4)) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(8.8) .color(theme.ink()) .build()) diff --git a/examples/src/main/java/com/demcha/examples/support/theme/BusinessTheme.java b/examples/src/main/java/com/demcha/examples/support/theme/BusinessTheme.java index 802723621..9a4a588eb 100644 --- a/examples/src/main/java/com/demcha/examples/support/theme/BusinessTheme.java +++ b/examples/src/main/java/com/demcha/examples/support/theme/BusinessTheme.java @@ -68,7 +68,7 @@ public static BusinessTheme classic() { .rule(new Color(210, 218, 230)) .build(); SpacingScale spacing = SpacingScale.defaultScale(); - TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA_BOLD, + TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA, 26, 16, 13, 11, 9); TablePreset table = tablePreset(palette, spacing); return new BusinessTheme("classic", palette, spacing, text, table, null); @@ -91,7 +91,7 @@ public static BusinessTheme modern() { .rule(new Color(212, 200, 178)) .build(); SpacingScale spacing = new SpacingScale(4.0, 8.0, 14.0, 22.0, 36.0); - TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA_BOLD, + TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA, 28, 17, 13, 11, 10); TablePreset table = tablePreset(palette, spacing); return new BusinessTheme("modern", palette, spacing, text, table, palette.surface()); @@ -140,7 +140,7 @@ public static BusinessTheme nordic() { .rule(new Color(220, 226, 232)) // very subtle cool line .build(); SpacingScale spacing = new SpacingScale(6.0, 12.0, 18.0, 28.0, 44.0); - TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA_BOLD, + TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA, 26, 16, 12, 10, 9); TablePreset table = tablePreset(palette, spacing); return new BusinessTheme("nordic", palette, spacing, text, table, null); @@ -192,7 +192,7 @@ public static BusinessTheme cinematic() { .rule(new Color(54, 64, 78)) // subtle on-dark rule .build(); SpacingScale spacing = new SpacingScale(4.0, 8.0, 14.0, 24.0, 40.0); - TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA_BOLD, + TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA, 30, 18, 14, 11, 9); TablePreset table = tablePreset(palette, spacing); return new BusinessTheme("cinematic", palette, spacing, text, table, palette.surface()); @@ -218,7 +218,7 @@ public static BusinessTheme monochrome() { .rule(new Color(0, 0, 0)) // bold rules .build(); SpacingScale spacing = new SpacingScale(4.0, 8.0, 12.0, 20.0, 36.0); - TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA_BOLD, + TextScale text = textScale(palette, FontName.HELVETICA, FontName.HELVETICA, 32, 20, 14, 11, 9); TablePreset table = tablePreset(palette, spacing); return new BusinessTheme("monochrome", palette, spacing, text, table, null); diff --git a/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterFileExample.java b/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterFileExample.java index 72873ce2a..348ee0117 100644 --- a/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterFileExample.java +++ b/examples/src/main/java/com/demcha/examples/templates/coverletter/CoverLetterFileExample.java @@ -6,6 +6,7 @@ import com.demcha.compose.document.style.DocumentColor; import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; import com.demcha.examples.support.theme.BusinessTheme; import com.demcha.compose.font.FontName; @@ -55,7 +56,8 @@ public static Path generate() throws Exception { .addParagraph(p -> p .text("Mariia Demchyshyn") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(22) .color(INK) .build()) @@ -88,7 +90,8 @@ public static Path generate() throws Exception { .addParagraph(p -> p .text("Re: Senior Engineer") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(10) .color(BRAND) .build()) @@ -102,7 +105,8 @@ public static Path generate() throws Exception { .addParagraph(p -> p .text("Northwind Systems Ltd.") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11.5) .color(INK) .build()) @@ -124,7 +128,8 @@ public static Path generate() throws Exception { .addRich(rich -> rich .plain("I help teams ship ") .style("designed PDFs as code", DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(13) .color(BRAND) .build()) @@ -138,7 +143,8 @@ public static Path generate() throws Exception { .addParagraph(p -> p .text("Dear Hiring Team,") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(11.5) .color(INK) .build()) @@ -192,7 +198,8 @@ public static Path generate() throws Exception { .addParagraph(p -> p .text("Mariia Demchyshyn") .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(13) .color(INK) .build()) @@ -237,7 +244,8 @@ private static void highlightCard(com.demcha.compose.document.dsl.SectionBuilder .addParagraph(p -> p .text(title) .textStyle(DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) + .decoration(DocumentTextDecoration.BOLD) .size(10.5) .color(INK) .build()) diff --git a/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java b/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java index 24e9addd8..00d9ee6bd 100644 --- a/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java +++ b/examples/src/main/java/com/demcha/examples/templates/proposal/CinematicProposalFileExample.java @@ -185,7 +185,7 @@ private static DocumentTextStyle regular(double size, DocumentColor color) { private static DocumentTextStyle bold(double size, DocumentColor color) { return DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) + .fontName(FontName.HELVETICA) .decoration(DocumentTextDecoration.BOLD) .size(size) .color(color) diff --git a/examples/src/test/java/com/demcha/examples/PptxCanonicalContentTest.java b/examples/src/test/java/com/demcha/examples/PptxCanonicalContentTest.java new file mode 100644 index 000000000..15defbe55 --- /dev/null +++ b/examples/src/test/java/com/demcha/examples/PptxCanonicalContentTest.java @@ -0,0 +1,513 @@ +package com.demcha.examples; + +import com.demcha.examples.flagships.MavenBannerPptxExample; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.imageio.ImageIO; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Pins the canonical PPTX comparison the asset gate will be built on. + * + *

A deck cannot be compared byte for byte. Every entry in the package carries the zip + * timestamp of the run that wrote it, so two renders of an unchanged document differ. The + * engine can pin those — both fixed backends take a {@code deterministic(...)} instant — + * but {@code buildPptx(Path)} routes through the convenience path, which resolves the + * backend from the provider and gives a caller no way to configure it. Until that seam + * exists, a gate comparing bytes would either fail forever or drop the decks.

+ * + *

Nor can it be compared part for part: three things about a package are decided by the + * machine rather than by the document, and all three were measured on these decks rather + * than assumed. See {@link #canonicalise(String, String, byte[])}. So the comparison is + * defined here as the sorted package parts reduced to what the document decides — every + * shape, relationship and run of text, with each freeform's path read where it lands on the + * slide and every image read pixel for pixel bar the one part {@link #UNSTABLE_RASTERS} + * names.

+ * + *

What this class does not do is hold a committed asset against a fresh render. A + * committed asset is rendered at the released version while a working tree renders the + * next one, so the two are different documents by construction; and a committed asset is + * not otherwise guaranteed to be level with the code that produces it. Comparing them + * needs the display version as an input and a way to refresh whatever has drifted — one + * job, belonging to the asset gate rather than to a unit test. The comparator is proven + * here so that the gate arrives with it already working.

+ */ +class PptxCanonicalContentTest { + + private static final Path COMMITTED = Path.of("..", "assets", "readme", "examples"); + + @BeforeAll + static void generateEveryExample() throws Exception { + GeneratedCatalogue.generateOnce(); + } + + /** + * The decks whose preview the repository commits. + * + *

Which decks those are is a decision, and this list plus {@link #UNPUBLISHED_DECKS} + * exist so that no deck can arrive without somebody making it. A subset alone is not + * enough: asserting that the curated decks are among the generated ones lets a new + * example ship a deck nobody has decided about, which is the same silence the pairing + * this replaced used to keep. Together the two lists cover the catalogue exactly, so a + * deck added, removed or renamed lands here first.

+ */ + private static final Set CURATED_DECKS = Set.of( + "business-report.pptx", + "financial-report.pptx", + "master-showcase.pptx", + "maven-banner.pptx", + "social-card.pptx", + "twin-output.pptx"); + + /** Decks the catalogue renders but the repository deliberately does not commit. */ + private static final Set UNPUBLISHED_DECKS = Set.of( + "engine-deck.pptx", + "linkedin-carousel.pptx"); + + @Test + void theTwoDeckListsDoNotOverlap() { + assertThat(CURATED_DECKS) + .describedAs("a deck cannot be both published and deliberately unpublished") + .doesNotContainAnyElementsOf(UNPUBLISHED_DECKS); + } + + @Test + void theCommittedDecksAreExactlyTheCuratedSubset() throws Exception { + Set committed = new TreeSet<>(); + try (var files = Files.list(COMMITTED)) { + files.map(path -> path.getFileName().toString()) + .filter(name -> name.endsWith(".pptx")) + .forEach(committed::add); + } + Map generated = new TreeMap<>(); + try (var decks = Files.walk(GeneratedCatalogue.ROOT)) { + decks.filter(path -> path.toString().endsWith(".pptx")) + .forEach(path -> { + Path clash = generated.put(path.getFileName().toString(), path); + if (clash != null) { + throw new IllegalStateException( + "two generated decks share the name " + path.getFileName() + + " (" + clash + " and " + path + "); the committed gallery is " + + "flat, so one would silently stand in for the other"); + } + }); + } + + assertThat(committed) + .describedAs("the committed decks and the curated list have to agree: a deck " + + "added to the folder without a decision, or removed from it without " + + "one, is exactly what this list exists to surface") + .isEqualTo(new TreeSet<>(CURATED_DECKS)); + Set accountedFor = new TreeSet<>(CURATED_DECKS); + accountedFor.addAll(UNPUBLISHED_DECKS); + assertThat(generated.keySet()) + .describedAs("the catalogue and the two lists have to cover each other exactly: a " + + "deck listed but not generated is a committed file nothing can refresh, " + + "and a deck generated but on neither list is one nobody has decided to " + + "publish or to leave out") + .isEqualTo(accountedFor); + } + + /** + * Rendering the same deck twice produces one document under this digest. + * + *

Only the equality is asserted. Whether the two files also differ in bytes depends + * on the clock — zip entry timestamps have two-second granularity, so two renders in + * quick succession can land on the same stamp and produce identical files, while two + * a moment apart do not. That is the reason the gate cannot hash the file, and it is + * also the reason it cannot be asserted: the property is real but intermittent, and a + * test that pins it fails on whichever machine happens to be fast.

+ */ + @Test + void theSameDeckRenderedTwiceIsOneDocument() throws Exception { + Path deck = GeneratedCatalogue.ROOT.resolve("flagships").resolve("maven-banner.pptx"); + assertThat(deck).exists(); + + String firstDigest = canonicalDigest(deck); + MavenBannerPptxExample.generate(); + + assertThat(canonicalDigest(deck)) + .describedAs("the same document rendered twice must be one document") + .isEqualTo(firstDigest); + } + + /** + * The same path drawn in a different box is one shape; a path that moves is not. + * + *

Dropping the box is the load-bearing half of the comparison, so it is checked in + * both directions: the three shapes below are the same freeform normalised the two ways + * two machines normalised it, and then genuinely moved. Absorbing the third as well + * would leave the guard reporting success on a deck whose artwork had shifted.

+ */ + @Test + void theBoxIsAbsorbedButAMoveIsNot() { + String box = freeform("", + "", ""); + String sameLineOtherBox = freeform( + "", + "", ""); + String movedLine = freeform("", + "", ""); + + assertThat(freeformsInSlideSpace(sameLineOtherBox)) + .describedAs("the same line from (1000,2000) to (1600,2400), normalised against " + + "an origin 50 EMU away — the difference two machines produce") + .isEqualTo(freeformsInSlideSpace(box)); + assertThat(freeformsInSlideSpace(movedLine)) + .describedAs("a line that actually moved by 50 EMU must not be absorbed") + .isNotEqualTo(freeformsInSlideSpace(box)); + } + + /** + * The rewrite reaches the markup POI actually writes, not just the shape of it. + * + *

{@link #theBoxIsAbsorbedButAMoveIsNot} works on markup written here, so it would + * still pass if POI reordered an attribute and the rewrite quietly stopped matching — + * leaving a comparison that absorbs nothing and a red gate nobody can read. A deck the + * repository commits has to come out changed.

+ */ + @Test + void theRewriteReachesRealMarkup() throws Exception { + String slide = part(COMMITTED.resolve("twin-output.pptx"), "ppt/slides/slide1.xml"); + + assertThat(freeformsInSlideSpace(slide)) + .describedAs("no freeform in a deck full of them was rewritten — the markup no " + + "longer looks the way the patterns expect") + .isNotEqualTo(slide); + } + + /** + * An image of the same size is not the same image. + * + *

Reading a raster part by its dimensions would let a logo be swapped, a screenshot + * be replaced or a watermark be retyped without the comparison noticing, which is a + * quiet way for a gate to report that nothing changed. Only the one part measured as + * machine-dependent is read that way; the fixtures below are the same watermark from + * two machines, so they are the closest two images this repository has, and even they + * must come out different when compared as images.

+ */ + @Test + void sameDimensionsButDifferentPixelsAreNotEqual() throws Exception { + BufferedImage windows = decode(WATERMARK_WINDOWS); + BufferedImage linux = decode(WATERMARK_LINUX); + assertThat(windows.getWidth()).isEqualTo(linux.getWidth()); + assertThat(windows.getHeight()).isEqualTo(linux.getHeight()); + + assertThat(canonicalise("business-report.pptx", WATERMARK_PART, fixture(WATERMARK_WINDOWS))) + .describedAs("two images of one size are not one image — and the deck this part " + + "sits in is not the one the allowlist names") + .isNotEqualTo(canonicalise("business-report.pptx", WATERMARK_PART, + fixture(WATERMARK_LINUX))); + } + + /** + * The named part absorbs the difference that was measured, and the fixtures carry it. + * + *

The two files are {@code ppt/media/image1.png} of the showcase deck rendered on + * Windows and on the runner: same glyphs in the same places, different coverage along + * their edges. Pinning them here keeps the allowlist honest — if the difference ever + * stops being antialiasing the entry stops being justified, and this is where that + * shows.

+ */ + @Test + void theNamedUnstableRasterAbsorbsTheMeasuredDifference() throws Exception { + byte[] windows = fixture(WATERMARK_WINDOWS); + byte[] linux = fixture(WATERMARK_LINUX); + assertThat(windows) + .describedAs("the fixtures have to be two renders, not one file twice") + .isNotEqualTo(linux); + + assertThat(canonicalise(WATERMARK_DECK, WATERMARK_PART, windows)) + .describedAs("the watermark named in the allowlist is the one difference the " + + "comparison is allowed to pass over") + .isEqualTo(canonicalise(WATERMARK_DECK, WATERMARK_PART, linux)); + } + + /** + * A part written with either line ending is one part. + * + *

The normalisation this pins is the difference that failed six decks on the runner, + * and it is the one canonicalisation whose absence shows up nowhere else: every test + * here runs on one machine, where both sides carry the same line ending and the bug + * hides.

+ */ + @Test + void theSamePartWithEitherLineEndingIsOnePart() throws Exception { + String declaration = ""; + byte[] unix = (declaration + "\n").getBytes(StandardCharsets.UTF_8); + byte[] windows = (declaration + "\r\n").getBytes(StandardCharsets.UTF_8); + + assertThat(canonicalise(WATERMARK_DECK, "ppt/slides/slide1.xml", windows)) + .describedAs("POI ends the XML declaration with the platform's line separator; " + + "that is the machine writing, not the document") + .isEqualTo(canonicalise(WATERMARK_DECK, "ppt/slides/slide1.xml", unix)); + } + + /** + * Every allowlisted part names a part that is really there. + * + *

A misspelled deck or part would match nothing, and an entry that matches nothing + * exempts nothing — the comparison would quietly go back to reading the watermark by + * its pixels and fail on a runner for a reason the entry was added to explain.

+ */ + @Test + void everyAllowlistedRasterExists() throws Exception { + for (String entry : new TreeSet<>(UNSTABLE_RASTERS)) { + String[] split = entry.split("!", 2); + assertThat(split).describedAs("%s is not deck!part", entry).hasSize(2); + for (Path deck : List.of(COMMITTED.resolve(split[0]), generatedDeck(split[0]))) { + assertThat(deck).describedAs("allowlisted deck %s is missing", split[0]).exists(); + assertThat(partNames(deck)) + .describedAs("allowlisted part %s is not in %s", split[1], deck) + .contains(split[1]); + } + } + } + + private static Path generatedDeck(String name) throws IOException { + try (var decks = Files.walk(GeneratedCatalogue.ROOT)) { + return decks.filter(path -> path.getFileName().toString().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "the catalogue no longer renders " + name)); + } + } + + private static Set partNames(Path pptx) throws IOException { + Set names = new TreeSet<>(); + try (ZipInputStream zip = + new ZipInputStream(new ByteArrayInputStream(Files.readAllBytes(pptx)))) { + for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { + names.add(entry.getName()); + } + } + return names; + } + + private static final String WATERMARK_WINDOWS = "watermark-windows.png"; + private static final String WATERMARK_LINUX = "watermark-linux.png"; + private static final String WATERMARK_DECK = "master-showcase.pptx"; + private static final String WATERMARK_PART = "ppt/media/image1.png"; + + private static byte[] fixture(String name) throws IOException { + try (var in = PptxCanonicalContentTest.class.getResourceAsStream("/pptx-media/" + name)) { + assertThat(in).describedAs("missing test fixture %s", name).isNotNull(); + return in.readAllBytes(); + } + } + + private static BufferedImage decode(String name) throws IOException { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(fixture(name))); + assertThat(image).describedAs("fixture %s does not decode as an image", name).isNotNull(); + return image; + } + + private static String part(Path pptx, String name) throws Exception { + try (ZipInputStream zip = + new ZipInputStream(new ByteArrayInputStream(Files.readAllBytes(pptx)))) { + for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { + if (entry.getName().equals(name)) { + return new String(zip.readAllBytes(), StandardCharsets.UTF_8) + .replace("\r\n", "\n"); + } + } + } + throw new IllegalStateException(pptx + " has no part " + name); + } + + private static String freeform(String xfrm, String from, String to) { + return "" + + "" + xfrm + "" + + "" + + "" + from + "" + to + "" + + ""; + } + + /** The package's parts and their contents, with everything the machine adds left out. */ + private static String canonicalDigest(Path pptx) throws Exception { + Map parts = new TreeMap<>(); + try (ZipInputStream zip = + new ZipInputStream(new ByteArrayInputStream(Files.readAllBytes(pptx)))) { + String deck = pptx.getFileName().toString(); + for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { + if (!entry.isDirectory()) { + parts.put(entry.getName(), + canonicalise(deck, entry.getName(), zip.readAllBytes())); + } + } + } + + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + parts.forEach((name, content) -> { + digest.update(name.getBytes(StandardCharsets.UTF_8)); + digest.update((byte) 0); + digest.update(content); + digest.update((byte) 0); + }); + return HexFormat.of().formatHex(digest.digest()); + } + + /** + * Reduces a part to what the document decides, dropping what the machine decides. + * + *

Three differences were measured between decks written on Windows and the same + * decks written on the Linux runner, none of them a change to the document:

+ * + *
    + *
  • Line endings. POI ends the XML declaration with the platform's line + * separator, so every XML part differs by one byte — sixteen parts of an + * otherwise untouched deck.
  • + *
  • Freeform boxes. The box a freeform declares, and the origin its path + * coordinates are measured from, are not stable: one icon came out with every + * point shifted by a constant 272 EMU across and 489 down, and its declared + * extent smaller by the same amount, so that the path landed on exactly the same + * place on the slide. The points are therefore compared where they land, and the + * box that only says how they were normalised is dropped. A freeform that + * actually moves still moves its points.
  • + *
  • Rasterised text. One embedded image — a text watermark — differed along + * the glyph edges alone: same glyphs, same positions, different antialiasing + * coverage. Nothing canonicalises that, so {@link #UNSTABLE_RASTERS} names the + * part and it alone is read by its dimensions. Every other image is compared + * pixel for pixel.
  • + *
+ */ + private static byte[] canonicalise(String deck, String name, byte[] content) + throws IOException { + if (name.startsWith("ppt/media/")) { + return image(content, UNSTABLE_RASTERS.contains(deck + "!" + name)); + } + if (!name.endsWith(".xml") && !name.endsWith(".rels")) { + return content; + } + String text = new String(content, StandardCharsets.UTF_8).replace("\r\n", "\n"); + return freeformsInSlideSpace(text).getBytes(StandardCharsets.UTF_8); + } + + /** + * The one raster part a machine is allowed to disagree about, as {@code deck!part}. + * + *

Naming it costs a line and buys the difference between a comparison that tolerates + * one measured artefact and one that stops reading images altogether. Anything added + * here stops being compared by content, so it wants the same measurement behind it that + * put this entry here.

+ */ + private static final Set UNSTABLE_RASTERS = + Set.of(WATERMARK_DECK + "!" + WATERMARK_PART); + + /** + * An image's pixels in one colour model, or — for a named unstable part — its size. + * + *

{@code getRGB} converts whatever the decoder produced into sRGB, so a part is + * compared by what it looks like rather than by how it was stored. A part that decodes + * to nothing is vector, and its bytes are its content.

+ */ + private static byte[] image(byte[] content, boolean sizeOnly) throws IOException { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(content)); + if (image == null) { + return content; + } + int width = image.getWidth(); + int height = image.getHeight(); + if (sizeOnly) { + return "%dx%d antialiasing not compared".formatted(width, height) + .getBytes(StandardCharsets.UTF_8); + } + ByteBuffer pixels = ByteBuffer.allocate(8 + 4 * width * height); + pixels.putInt(width).putInt(height); + for (int argb : image.getRGB(0, 0, width, height, null, 0, width)) { + pixels.putInt(argb); + } + return pixels.array(); + } + + private static final Pattern SHAPE = Pattern.compile(".*?", Pattern.DOTALL); + private static final Pattern OFFSET = Pattern.compile(""); + private static final Pattern EXTENT = Pattern.compile(""); + private static final Pattern PATH_TAG = Pattern.compile("]*)>"); + private static final Pattern POINT = Pattern.compile(""); + private static final Pattern BOX_ATTRIBUTE = Pattern.compile(" [wh]=\"\\d+\""); + private static final Pattern PATH_WIDTH = Pattern.compile("\\bw=\"(\\d+)\""); + private static final Pattern PATH_HEIGHT = Pattern.compile("\\bh=\"(\\d+)\""); + + /** Reads each freeform's path where it lands on the slide rather than inside its box. */ + private static String freeformsInSlideSpace(String xml) { + Matcher shapes = SHAPE.matcher(xml); + StringBuilder out = new StringBuilder(); + while (shapes.find()) { + shapes.appendReplacement(out, Matcher.quoteReplacement(inSlideSpace(shapes.group()))); + } + shapes.appendTail(out); + return out.toString(); + } + + private static String inSlideSpace(String shape) { + Matcher offset = OFFSET.matcher(shape); + Matcher extent = EXTENT.matcher(shape); + Matcher pathTag = PATH_TAG.matcher(shape); + if (!offset.find() || !extent.find() || !pathTag.find()) { + return shape; + } + long pathWidth = attribute(PATH_WIDTH, pathTag.group(1)); + long pathHeight = attribute(PATH_HEIGHT, pathTag.group(1)); + if (pathWidth <= 0 || pathHeight <= 0) { + return shape; + } + long offsetX = Long.parseLong(offset.group(1)); + long offsetY = Long.parseLong(offset.group(2)); + double scaleX = Long.parseLong(extent.group(1)) / (double) pathWidth; + double scaleY = Long.parseLong(extent.group(2)) / (double) pathHeight; + + Matcher points = POINT.matcher(shape); + StringBuilder out = new StringBuilder(); + while (points.find()) { + long x = offsetX + Math.round(Long.parseLong(points.group(1)) * scaleX); + long y = offsetY + Math.round(Long.parseLong(points.group(2)) * scaleY); + points.appendReplacement(out, + Matcher.quoteReplacement("".formatted(x, y))); + } + points.appendTail(out); + + String slideSpace = OFFSET.matcher(out.toString()).replaceFirst(""); + slideSpace = EXTENT.matcher(slideSpace).replaceFirst(""); + return withoutPathBox(slideSpace); + } + + private static String withoutPathBox(String shape) { + Matcher tags = PATH_TAG.matcher(shape); + StringBuilder out = new StringBuilder(); + while (tags.find()) { + String rest = BOX_ATTRIBUTE.matcher(" " + tags.group(1)).replaceAll("").trim(); + tags.appendReplacement(out, Matcher.quoteReplacement( + rest.isEmpty() ? "" : "")); + } + tags.appendTail(out); + return out.toString(); + } + + private static long attribute(Pattern attribute, String attributes) { + Matcher value = attribute.matcher(attributes); + return value.find() ? Long.parseLong(value.group(1)) : -1; + } +} diff --git a/examples/src/test/resources/layout-snapshots/flagships/engine-deck-v2.json b/examples/src/test/resources/layout-snapshots/flagships/engine-deck-v2.json index e15278770..f14fc6166 100644 --- a/examples/src/test/resources/layout-snapshots/flagships/engine-deck-v2.json +++ b/examples/src/test/resources/layout-snapshots/flagships/engine-deck-v2.json @@ -1435,11 +1435,11 @@ "computedY" : 351.625, "placementX" : 52.0, "placementY" : 351.625, - "placementWidth" : 245.105, + "placementWidth" : 260.61, "placementHeight" : 32.375, "startPage" : 0, "endPage" : 0, - "contentWidth" : 245.105, + "contentWidth" : 260.61, "contentHeight" : 32.375, "margin" : { "top" : 0.0, @@ -1465,11 +1465,11 @@ "computedY" : 305.625, "placementX" : 52.0, "placementY" : 305.625, - "placementWidth" : 394.905, + "placementWidth" : 425.88, "placementHeight" : 32.375, "startPage" : 0, "endPage" : 0, - "contentWidth" : 394.905, + "contentWidth" : 425.88, "contentHeight" : 32.375, "margin" : { "top" : 0.0, @@ -3385,11 +3385,11 @@ "computedY" : 455.28, "placementX" : 42.0, "placementY" : 455.28, - "placementWidth" : 424.442, + "placementWidth" : 449.903, "placementHeight" : 21.275, "startPage" : 1, "endPage" : 1, - "contentWidth" : 424.442, + "contentWidth" : 449.903, "contentHeight" : 21.275, "margin" : { "top" : 0.0, @@ -5215,11 +5215,11 @@ "computedY" : 455.28, "placementX" : 42.0, "placementY" : 455.28, - "placementWidth" : 290.214, + "placementWidth" : 310.569, "placementHeight" : 21.275, "startPage" : 2, "endPage" : 2, - "contentWidth" : 290.214, + "contentWidth" : 310.569, "contentHeight" : 21.275, "margin" : { "top" : 0.0, @@ -6865,11 +6865,11 @@ "computedY" : 457.28, "placementX" : 42.0, "placementY" : 457.28, - "placementWidth" : 374.532, + "placementWidth" : 401.35, "placementHeight" : 21.275, "startPage" : 3, "endPage" : 3, - "contentWidth" : 374.532, + "contentWidth" : 401.35, "contentHeight" : 21.275, "margin" : { "top" : 0.0, @@ -6985,11 +6985,11 @@ "computedY" : 403.785, "placementX" : 54.0, "placementY" : 403.785, - "placementWidth" : 56.912, + "placementWidth" : 58.704, "placementHeight" : 14.8, "startPage" : 3, "endPage" : 3, - "contentWidth" : 56.912, + "contentWidth" : 58.704, "contentHeight" : 14.8, "margin" : { "top" : 0.0, @@ -7105,11 +7105,11 @@ "computedY" : 403.785, "placementX" : 275.5, "placementY" : 403.785, - "placementWidth" : 63.136, + "placementWidth" : 64.912, "placementHeight" : 14.8, "startPage" : 3, "endPage" : 3, - "contentWidth" : 63.136, + "contentWidth" : 64.912, "contentHeight" : 14.8, "margin" : { "top" : 0.0, @@ -7495,11 +7495,11 @@ "computedY" : 342.174, "placementX" : 55.0, "placementY" : 342.174, - "placementWidth" : 170.78, + "placementWidth" : 181.01, "placementHeight" : 8.51, "startPage" : 3, "endPage" : 3, - "contentWidth" : 170.78, + "contentWidth" : 181.01, "contentHeight" : 8.51, "margin" : { "top" : 0.0, @@ -7585,11 +7585,11 @@ "computedY" : 342.174, "placementX" : 499.0, "placementY" : 342.174, - "placementWidth" : 173.337, + "placementWidth" : 185.582, "placementHeight" : 8.51, "startPage" : 3, "endPage" : 3, - "contentWidth" : 173.337, + "contentWidth" : 185.582, "contentHeight" : 8.51, "margin" : { "top" : 0.0, @@ -7795,11 +7795,11 @@ "computedY" : 457.28, "placementX" : 42.0, "placementY" : 457.28, - "placementWidth" : 439.783, + "placementWidth" : 470.327, "placementHeight" : 21.275, "startPage" : 4, "endPage" : 4, - "contentWidth" : 439.783, + "contentWidth" : 470.327, "contentHeight" : 21.275, "margin" : { "top" : 0.0, @@ -7915,11 +7915,11 @@ "computedY" : 409.075, "placementX" : 55.0, "placementY" : 409.075, - "placementWidth" : 175.37, + "placementWidth" : 187.652, "placementHeight" : 8.51, "startPage" : 4, "endPage" : 4, - "contentWidth" : 175.37, + "contentWidth" : 187.652, "contentHeight" : 8.51, "margin" : { "top" : 0.0, @@ -8005,11 +8005,11 @@ "computedY" : 409.075, "placementX" : 499.0, "placementY" : 409.075, - "placementWidth" : 193.78, + "placementWidth" : 207.58, "placementHeight" : 8.51, "startPage" : 4, "endPage" : 4, - "contentWidth" : 193.78, + "contentWidth" : 207.58, "contentHeight" : 8.51, "margin" : { "top" : 0.0, @@ -8125,11 +8125,11 @@ "computedY" : 190.262, "placementX" : 54.0, "placementY" : 190.262, - "placementWidth" : 36.69, + "placementWidth" : 37.53, "placementHeight" : 13.875, "startPage" : 4, "endPage" : 4, - "contentWidth" : 36.69, + "contentWidth" : 37.53, "contentHeight" : 13.875, "margin" : { "top" : 0.0, @@ -8215,11 +8215,11 @@ "computedY" : 190.262, "placementX" : 349.333, "placementY" : 190.262, - "placementWidth" : 36.69, + "placementWidth" : 37.53, "placementHeight" : 13.875, "startPage" : 4, "endPage" : 4, - "contentWidth" : 36.69, + "contentWidth" : 37.53, "contentHeight" : 13.875, "margin" : { "top" : 0.0, diff --git a/examples/src/test/resources/layout-snapshots/flagships/engine-deck.json b/examples/src/test/resources/layout-snapshots/flagships/engine-deck.json index d6b695fc0..041043fdf 100644 --- a/examples/src/test/resources/layout-snapshots/flagships/engine-deck.json +++ b/examples/src/test/resources/layout-snapshots/flagships/engine-deck.json @@ -1045,11 +1045,11 @@ "computedY" : 368.137, "placementX" : 34.0, "placementY" : 368.137, - "placementWidth" : 151.948, + "placementWidth" : 158.474, "placementHeight" : 14.8, "startPage" : 0, "endPage" : 0, - "contentWidth" : 151.948, + "contentWidth" : 158.474, "contentHeight" : 14.8, "margin" : { "top" : 0.0, @@ -1525,11 +1525,11 @@ "computedY" : 368.137, "placementX" : 240.526, "placementY" : 368.137, - "placementWidth" : 155.575, + "placementWidth" : 164.207, "placementHeight" : 14.8, "startPage" : 0, "endPage" : 0, - "contentWidth" : 155.575, + "contentWidth" : 164.207, "contentHeight" : 14.8, "margin" : { "top" : 0.0, @@ -1705,11 +1705,11 @@ "computedY" : 303.343, "placementX" : 272.526, "placementY" : 303.343, - "placementWidth" : 25.517, + "placementWidth" : 27.863, "placementHeight" : 7.863, "startPage" : 0, "endPage" : 0, - "contentWidth" : 25.517, + "contentWidth" : 27.863, "contentHeight" : 7.863, "margin" : { "top" : 0.0, @@ -1825,11 +1825,11 @@ "computedY" : 265.343, "placementX" : 272.526, "placementY" : 265.343, - "placementWidth" : 40.162, + "placementWidth" : 43.452, "placementHeight" : 7.863, "startPage" : 0, "endPage" : 0, - "contentWidth" : 40.162, + "contentWidth" : 43.452, "contentHeight" : 7.863, "margin" : { "top" : 0.0, @@ -1945,11 +1945,11 @@ "computedY" : 227.343, "placementX" : 272.526, "placementY" : 227.343, - "placementWidth" : 30.702, + "placementWidth" : 32.122, "placementHeight" : 7.863, "startPage" : 0, "endPage" : 0, - "contentWidth" : 30.702, + "contentWidth" : 32.122, "contentHeight" : 7.863, "margin" : { "top" : 0.0, @@ -2155,11 +2155,11 @@ "computedY" : 303.343, "placementX" : 373.526, "placementY" : 303.343, - "placementWidth" : 48.186, + "placementWidth" : 51.944, "placementHeight" : 7.863, "startPage" : 0, "endPage" : 0, - "contentWidth" : 48.186, + "contentWidth" : 51.944, "contentHeight" : 7.863, "margin" : { "top" : 0.0, @@ -2515,11 +2515,11 @@ "computedY" : 265.343, "placementX" : 373.526, "placementY" : 265.343, - "placementWidth" : 59.058, + "placementWidth" : 63.291, "placementHeight" : 7.863, "startPage" : 0, "endPage" : 0, - "contentWidth" : 59.058, + "contentWidth" : 63.291, "contentHeight" : 7.863, "margin" : { "top" : 0.0, @@ -2635,11 +2635,11 @@ "computedY" : 227.343, "placementX" : 373.526, "placementY" : 227.343, - "placementWidth" : 16.533, + "placementWidth" : 17.0, "placementHeight" : 7.863, "startPage" : 0, "endPage" : 0, - "contentWidth" : 16.533, + "contentWidth" : 17.0, "contentHeight" : 7.863, "margin" : { "top" : 0.0, @@ -2722,15 +2722,15 @@ "depth" : 4, "layer" : 4, "computedX" : 475.109, - "computedY" : 206.137, + "computedY" : 194.112, "placementX" : 475.109, - "placementY" : 206.137, - "placementWidth" : 119.461, - "placementHeight" : 176.8, + "placementY" : 194.112, + "placementWidth" : 110.0, + "placementHeight" : 188.825, "startPage" : 0, "endPage" : 0, - "contentWidth" : 119.461, - "contentHeight" : 176.8, + "contentWidth" : 110.0, + "contentHeight" : 188.825, "margin" : { "top" : 0.0, "right" : 0.0, @@ -2752,15 +2752,15 @@ "depth" : 5, "layer" : 5, "computedX" : 475.109, - "computedY" : 368.137, + "computedY" : 356.112, "placementX" : 475.109, - "placementY" : 368.137, - "placementWidth" : 119.461, - "placementHeight" : 14.8, + "placementY" : 356.112, + "placementWidth" : 62.339, + "placementHeight" : 26.825, "startPage" : 0, "endPage" : 0, - "contentWidth" : 119.461, - "contentHeight" : 14.8, + "contentWidth" : 62.339, + "contentHeight" : 26.825, "margin" : { "top" : 0.0, "right" : 0.0, @@ -2782,9 +2782,9 @@ "depth" : 5, "layer" : 5, "computedX" : 475.109, - "computedY" : 314.137, + "computedY" : 302.112, "placementX" : 475.109, - "placementY" : 314.137, + "placementY" : 302.112, "placementWidth" : 110.0, "placementHeight" : 44.0, "startPage" : 0, @@ -2812,9 +2812,9 @@ "depth" : 6, "layer" : 6, "computedX" : 487.109, - "computedY" : 327.137, + "computedY" : 315.112, "placementX" : 487.109, - "placementY" : 327.137, + "placementY" : 315.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -2842,9 +2842,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 327.137, + "computedY" : 315.112, "placementX" : 487.109, - "placementY" : 327.137, + "placementY" : 315.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -2872,9 +2872,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 327.137, + "computedY" : 315.112, "placementX" : 487.109, - "placementY" : 327.137, + "placementY" : 315.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -2902,9 +2902,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 327.137, + "computedY" : 315.112, "placementX" : 487.109, - "placementY" : 327.137, + "placementY" : 315.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -2932,14 +2932,14 @@ "depth" : 6, "layer" : 6, "computedX" : 512.109, - "computedY" : 338.743, + "computedY" : 326.718, "placementX" : 512.109, - "placementY" : 338.743, - "placementWidth" : 51.215, + "placementY" : 326.718, + "placementWidth" : 52.792, "placementHeight" : 8.787, "startPage" : 0, "endPage" : 0, - "contentWidth" : 51.215, + "contentWidth" : 52.792, "contentHeight" : 8.787, "margin" : { "top" : 0.0, @@ -2962,14 +2962,14 @@ "depth" : 6, "layer" : 6, "computedX" : 512.109, - "computedY" : 324.668, + "computedY" : 312.643, "placementX" : 512.109, - "placementY" : 324.668, - "placementWidth" : 66.285, + "placementY" : 312.643, + "placementWidth" : 72.098, "placementHeight" : 6.937, "startPage" : 0, "endPage" : 0, - "contentWidth" : 66.285, + "contentWidth" : 72.098, "contentHeight" : 6.937, "margin" : { "top" : 0.0, @@ -2992,9 +2992,9 @@ "depth" : 5, "layer" : 5, "computedX" : 475.109, - "computedY" : 260.137, + "computedY" : 248.112, "placementX" : 475.109, - "placementY" : 260.137, + "placementY" : 248.112, "placementWidth" : 110.0, "placementHeight" : 44.0, "startPage" : 0, @@ -3022,9 +3022,9 @@ "depth" : 6, "layer" : 6, "computedX" : 487.109, - "computedY" : 273.137, + "computedY" : 261.112, "placementX" : 487.109, - "placementY" : 273.137, + "placementY" : 261.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3052,9 +3052,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 273.137, + "computedY" : 261.112, "placementX" : 487.109, - "placementY" : 273.137, + "placementY" : 261.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3082,9 +3082,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 273.137, + "computedY" : 261.112, "placementX" : 487.109, - "placementY" : 273.137, + "placementY" : 261.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3112,9 +3112,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 273.137, + "computedY" : 261.112, "placementX" : 487.109, - "placementY" : 273.137, + "placementY" : 261.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3142,14 +3142,14 @@ "depth" : 6, "layer" : 6, "computedX" : 512.109, - "computedY" : 284.743, + "computedY" : 272.718, "placementX" : 512.109, - "placementY" : 284.743, - "placementWidth" : 56.487, + "placementY" : 272.718, + "placementWidth" : 59.119, "placementHeight" : 8.787, "startPage" : 0, "endPage" : 0, - "contentWidth" : 56.487, + "contentWidth" : 59.119, "contentHeight" : 8.787, "margin" : { "top" : 0.0, @@ -3172,14 +3172,14 @@ "depth" : 6, "layer" : 6, "computedX" : 512.109, - "computedY" : 270.668, + "computedY" : 258.643, "placementX" : 512.109, - "placementY" : 270.668, - "placementWidth" : 54.188, + "placementY" : 258.643, + "placementWidth" : 58.35, "placementHeight" : 6.937, "startPage" : 0, "endPage" : 0, - "contentWidth" : 54.188, + "contentWidth" : 58.35, "contentHeight" : 6.937, "margin" : { "top" : 0.0, @@ -3202,9 +3202,9 @@ "depth" : 5, "layer" : 5, "computedX" : 475.109, - "computedY" : 206.137, + "computedY" : 194.112, "placementX" : 475.109, - "placementY" : 206.137, + "placementY" : 194.112, "placementWidth" : 110.0, "placementHeight" : 44.0, "startPage" : 0, @@ -3232,9 +3232,9 @@ "depth" : 6, "layer" : 6, "computedX" : 487.109, - "computedY" : 219.137, + "computedY" : 207.112, "placementX" : 487.109, - "placementY" : 219.137, + "placementY" : 207.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3262,9 +3262,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 219.137, + "computedY" : 207.112, "placementX" : 487.109, - "placementY" : 219.137, + "placementY" : 207.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3292,9 +3292,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 219.137, + "computedY" : 207.112, "placementX" : 487.109, - "placementY" : 219.137, + "placementY" : 207.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3322,9 +3322,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 219.137, + "computedY" : 207.112, "placementX" : 487.109, - "placementY" : 219.137, + "placementY" : 207.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3352,9 +3352,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 219.137, + "computedY" : 207.112, "placementX" : 487.109, - "placementY" : 219.137, + "placementY" : 207.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3382,9 +3382,9 @@ "depth" : 7, "layer" : 7, "computedX" : 487.109, - "computedY" : 219.137, + "computedY" : 207.112, "placementX" : 487.109, - "placementY" : 219.137, + "placementY" : 207.112, "placementWidth" : 18.0, "placementHeight" : 18.0, "startPage" : 0, @@ -3412,9 +3412,9 @@ "depth" : 6, "layer" : 6, "computedX" : 512.109, - "computedY" : 230.743, + "computedY" : 218.718, "placementX" : 512.109, - "placementY" : 230.743, + "placementY" : 218.718, "placementWidth" : 24.814, "placementHeight" : 8.787, "startPage" : 0, @@ -3442,14 +3442,14 @@ "depth" : 6, "layer" : 6, "computedX" : 512.109, - "computedY" : 216.668, + "computedY" : 204.643, "placementX" : 512.109, - "placementY" : 216.668, - "placementWidth" : 27.518, + "placementY" : 204.643, + "placementWidth" : 29.175, "placementHeight" : 6.937, "startPage" : 0, "endPage" : 0, - "contentWidth" : 27.518, + "contentWidth" : 29.175, "contentHeight" : 6.937, "margin" : { "top" : 0.0, @@ -3565,11 +3565,11 @@ "computedY" : 368.137, "placementX" : 625.522, "placementY" : 368.137, - "placementWidth" : 118.72, + "placementWidth" : 123.023, "placementHeight" : 14.8, "startPage" : 0, "endPage" : 0, - "contentWidth" : 118.72, + "contentWidth" : 123.023, "contentHeight" : 14.8, "margin" : { "top" : 0.0, @@ -4045,11 +4045,11 @@ "computedY" : 113.316, "placementX" : 89.486, "placementY" : 113.316, - "placementWidth" : 55.974, + "placementWidth" : 59.128, "placementHeight" : 8.787, "startPage" : 0, "endPage" : 0, - "contentWidth" : 55.974, + "contentWidth" : 59.128, "contentHeight" : 8.787, "margin" : { "top" : 0.0, @@ -4225,11 +4225,11 @@ "computedY" : 113.316, "placementX" : 288.459, "placementY" : 113.316, - "placementWidth" : 61.769, + "placementWidth" : 64.933, "placementHeight" : 8.787, "startPage" : 0, "endPage" : 0, - "contentWidth" : 61.769, + "contentWidth" : 64.933, "contentHeight" : 8.787, "margin" : { "top" : 0.0, @@ -4405,11 +4405,11 @@ "computedY" : 113.316, "placementX" : 487.431, "placementY" : 113.316, - "placementWidth" : 38.817, + "placementWidth" : 39.881, "placementHeight" : 8.787, "startPage" : 0, "endPage" : 0, - "contentWidth" : 38.817, + "contentWidth" : 39.881, "contentHeight" : 8.787, "margin" : { "top" : 0.0, @@ -4585,11 +4585,11 @@ "computedY" : 113.316, "placementX" : 686.404, "placementY" : 113.316, - "placementWidth" : 51.737, + "placementWidth" : 54.378, "placementHeight" : 8.787, "startPage" : 0, "endPage" : 0, - "contentWidth" : 51.737, + "contentWidth" : 54.378, "contentHeight" : 8.787, "margin" : { "top" : 0.0, @@ -4645,11 +4645,11 @@ "computedY" : 531.301, "placementX" : 16.0, "placementY" : 531.301, - "placementWidth" : 302.13, + "placementWidth" : 320.094, "placementHeight" : 31.975, "startPage" : 1, "endPage" : 1, - "contentWidth" : 302.13, + "contentWidth" : 320.094, "contentHeight" : 31.975, "margin" : { "top" : 0.0, @@ -4675,11 +4675,11 @@ "computedY" : 554.951, "placementX" : 16.0, "placementY" : 554.951, - "placementWidth" : 69.003, + "placementWidth" : 69.498, "placementHeight" : 8.325, "startPage" : 1, "endPage" : 1, - "contentWidth" : 69.003, + "contentWidth" : 69.498, "contentHeight" : 8.325, "margin" : { "top" : 0.0, @@ -4705,11 +4705,11 @@ "computedY" : 531.301, "placementX" : 16.0, "placementY" : 531.301, - "placementWidth" : 302.13, + "placementWidth" : 320.094, "placementHeight" : 16.65, "startPage" : 1, "endPage" : 1, - "contentWidth" : 302.13, + "contentWidth" : 320.094, "contentHeight" : 16.65, "margin" : { "top" : 0.0, @@ -5965,11 +5965,11 @@ "computedY" : 305.188, "placementX" : 32.0, "placementY" : 305.188, - "placementWidth" : 63.558, + "placementWidth" : 69.685, "placementHeight" : 10.175, "startPage" : 1, "endPage" : 1, - "contentWidth" : 63.558, + "contentWidth" : 69.685, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -6055,11 +6055,11 @@ "computedY" : 305.188, "placementX" : 307.297, "placementY" : 305.188, - "placementWidth" : 89.254, + "placementWidth" : 95.964, "placementHeight" : 10.175, "startPage" : 1, "endPage" : 1, - "contentWidth" : 89.254, + "contentWidth" : 95.964, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -6145,11 +6145,11 @@ "computedY" : 305.188, "placementX" : 582.593, "placementY" : 305.188, - "placementWidth" : 83.754, + "placementWidth" : 91.069, "placementHeight" : 10.175, "startPage" : 1, "endPage" : 1, - "contentWidth" : 83.754, + "contentWidth" : 91.069, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -6235,11 +6235,11 @@ "computedY" : 531.301, "placementX" : 16.0, "placementY" : 531.301, - "placementWidth" : 223.092, + "placementWidth" : 239.058, "placementHeight" : 31.975, "startPage" : 2, "endPage" : 2, - "contentWidth" : 223.092, + "contentWidth" : 239.058, "contentHeight" : 31.975, "margin" : { "top" : 0.0, @@ -6265,11 +6265,11 @@ "computedY" : 554.951, "placementX" : 16.0, "placementY" : 554.951, - "placementWidth" : 70.002, + "placementWidth" : 71.487, "placementHeight" : 8.325, "startPage" : 2, "endPage" : 2, - "contentWidth" : 70.002, + "contentWidth" : 71.487, "contentHeight" : 8.325, "margin" : { "top" : 0.0, @@ -6295,11 +6295,11 @@ "computedY" : 531.301, "placementX" : 16.0, "placementY" : 531.301, - "placementWidth" : 223.092, + "placementWidth" : 239.058, "placementHeight" : 16.65, "startPage" : 2, "endPage" : 2, - "contentWidth" : 223.092, + "contentWidth" : 239.058, "contentHeight" : 16.65, "margin" : { "top" : 0.0, @@ -6475,11 +6475,11 @@ "computedY" : 324.291, "placementX" : 30.0, "placementY" : 324.291, - "placementWidth" : 235.95, + "placementWidth" : 250.03, "placementHeight" : 10.175, "startPage" : 2, "endPage" : 2, - "contentWidth" : 235.95, + "contentWidth" : 250.03, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -6565,11 +6565,11 @@ "computedY" : 324.291, "placementX" : 442.945, "placementY" : 324.291, - "placementWidth" : 232.914, + "placementWidth" : 245.751, "placementHeight" : 10.175, "startPage" : 2, "endPage" : 2, - "contentWidth" : 232.914, + "contentWidth" : 245.751, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -6685,11 +6685,11 @@ "computedY" : 531.301, "placementX" : 16.0, "placementY" : 531.301, - "placementWidth" : 224.064, + "placementWidth" : 238.068, "placementHeight" : 31.975, "startPage" : 3, "endPage" : 3, - "contentWidth" : 224.064, + "contentWidth" : 238.068, "contentHeight" : 31.975, "margin" : { "top" : 0.0, @@ -6715,11 +6715,11 @@ "computedY" : 554.951, "placementX" : 16.0, "placementY" : 554.951, - "placementWidth" : 39.51, + "placementWidth" : 40.5, "placementHeight" : 8.325, "startPage" : 3, "endPage" : 3, - "contentWidth" : 39.51, + "contentWidth" : 40.5, "contentHeight" : 8.325, "margin" : { "top" : 0.0, @@ -6745,11 +6745,11 @@ "computedY" : 531.301, "placementX" : 16.0, "placementY" : 531.301, - "placementWidth" : 224.064, + "placementWidth" : 238.068, "placementHeight" : 16.65, "startPage" : 3, "endPage" : 3, - "contentWidth" : 224.064, + "contentWidth" : 238.068, "contentHeight" : 16.65, "margin" : { "top" : 0.0, @@ -6895,11 +6895,11 @@ "computedY" : 453.916, "placementX" : 30.0, "placementY" : 453.916, - "placementWidth" : 163.207, + "placementWidth" : 173.613, "placementHeight" : 10.175, "startPage" : 3, "endPage" : 3, - "contentWidth" : 163.207, + "contentWidth" : 173.613, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -6985,11 +6985,11 @@ "computedY" : 453.916, "placementX" : 442.945, "placementY" : 453.916, - "placementWidth" : 157.729, + "placementWidth" : 165.671, "placementHeight" : 10.175, "startPage" : 3, "endPage" : 3, - "contentWidth" : 157.729, + "contentWidth" : 165.671, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -7105,11 +7105,11 @@ "computedY" : 241.857, "placementX" : 30.0, "placementY" : 241.857, - "placementWidth" : 186.461, + "placementWidth" : 197.45, "placementHeight" : 10.175, "startPage" : 3, "endPage" : 3, - "contentWidth" : 186.461, + "contentWidth" : 197.45, "contentHeight" : 10.175, "margin" : { "top" : 0.0, @@ -7195,11 +7195,11 @@ "computedY" : 241.857, "placementX" : 442.945, "placementY" : 241.857, - "placementWidth" : 265.947, + "placementWidth" : 284.845, "placementHeight" : 10.175, "startPage" : 3, "endPage" : 3, - "contentWidth" : 265.947, + "contentWidth" : 284.845, "contentHeight" : 10.175, "margin" : { "top" : 0.0, diff --git a/examples/src/test/resources/layout-snapshots/flagships/financial-report.json b/examples/src/test/resources/layout-snapshots/flagships/financial-report.json index 0730688c0..2da699a7e 100644 --- a/examples/src/test/resources/layout-snapshots/flagships/financial-report.json +++ b/examples/src/test/resources/layout-snapshots/flagships/financial-report.json @@ -205,11 +205,11 @@ "computedY" : 806.315, "placementX" : 189.354, "placementY" : 806.315, - "placementWidth" : 174.249, + "placementWidth" : 184.794, "placementHeight" : 17.575, "startPage" : 0, "endPage" : 0, - "contentWidth" : 174.249, + "contentWidth" : 184.794, "contentHeight" : 17.575, "margin" : { "top" : 0.0, @@ -235,11 +235,11 @@ "computedY" : 786.74, "placementX" : 189.354, "placementY" : 786.74, - "placementWidth" : 210.121, + "placementWidth" : 226.974, "placementHeight" : 17.575, "startPage" : 0, "endPage" : 0, - "contentWidth" : 210.121, + "contentWidth" : 226.974, "contentHeight" : 17.575, "margin" : { "top" : 0.0, diff --git a/examples/src/test/resources/layout-snapshots/flagships/twin-output.json b/examples/src/test/resources/layout-snapshots/flagships/twin-output.json index 3cf7c4dc3..b461ac69b 100644 --- a/examples/src/test/resources/layout-snapshots/flagships/twin-output.json +++ b/examples/src/test/resources/layout-snapshots/flagships/twin-output.json @@ -925,11 +925,11 @@ "computedY" : 376.4, "placementX" : 52.0, "placementY" : 376.4, - "placementWidth" : 188.512, + "placementWidth" : 202.688, "placementHeight" : 29.6, "startPage" : 0, "endPage" : 0, - "contentWidth" : 188.512, + "contentWidth" : 202.688, "contentHeight" : 29.6, "margin" : { "top" : 0.0, @@ -955,11 +955,11 @@ "computedY" : 334.4, "placementX" : 52.0, "placementY" : 334.4, - "placementWidth" : 231.2, + "placementWidth" : 245.408, "placementHeight" : 29.6, "startPage" : 0, "endPage" : 0, - "contentWidth" : 231.2, + "contentWidth" : 245.408, "contentHeight" : 29.6, "margin" : { "top" : 0.0, diff --git a/examples/src/test/resources/pptx-media/watermark-linux.png b/examples/src/test/resources/pptx-media/watermark-linux.png new file mode 100644 index 000000000..1a1ef5bcc Binary files /dev/null and b/examples/src/test/resources/pptx-media/watermark-linux.png differ diff --git a/examples/src/test/resources/pptx-media/watermark-windows.png b/examples/src/test/resources/pptx-media/watermark-windows.png new file mode 100644 index 000000000..63621d6f2 Binary files /dev/null and b/examples/src/test/resources/pptx-media/watermark-windows.png differ