idml 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfe99408096a550d69277aae9bf709c0dcb0257f17756fcef498c9ea3982a603
4
- data.tar.gz: 83fc58f40dccb82324b6098aab6665765cd0e1a08b12ade7e08f6d600c6e69c3
3
+ metadata.gz: bf684927e9227982c1773b71a6697b40ed9d93263eb1a272e413008ba6d2009b
4
+ data.tar.gz: 4fbd6e8ab4df4ba8281fb5353df9474348fe77b021703118c63c3f5d5a9f5ca1
5
5
  SHA512:
6
- metadata.gz: 8b9646bd611f3618e6a7c3c689a402b368b231f66f6f06430098f9357179d4b79ae0884058b20c92df03dce6e65aa7cf74c245bb8100a772a9454c37f310b4bd
7
- data.tar.gz: 7b639edc906f60c9e82128bf599966cd37e2fb0458e06e62158d14767d2293d918bed5fe75163cd2caa2cb20fed7174a0c18a709ac722a673b9099532e8f5b33
6
+ metadata.gz: 61d12e84a29ccefd212d6f09221eee09045b8ae18c8736bab98fea27cfdc0073151e8ffa53eadf18508af7bf104ff44e171b98f2fdd8e6d96019191792d594c9
7
+ data.tar.gz: 26f5541332df26f45f169da739c7c47b13f483a1c42c690c85867e8f3151522b0440d41bc7b502e680ff79ab00ac09ad791f9308cf9a0d7d16acb3a5ae87567c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- idml (0.4.8)
4
+ idml (0.4.9)
5
5
  bigdecimal
6
6
  lutaml-model (~> 0.8.18)
7
7
  pdfrb
@@ -183,7 +183,7 @@ CHECKSUMS
183
183
  ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
184
184
  ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
185
185
  ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
186
- idml (0.4.8)
186
+ idml (0.4.9)
187
187
  json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
188
188
  language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
189
189
  lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
@@ -0,0 +1,142 @@
1
+ # TODO PDF 89: Deep audit — idml-generated PDF vs InDesign PDF
2
+
3
+ ## Status: COMPLETE (audit); structural differences documented
4
+
5
+ ## TL;DR
6
+
7
+ **No** — the generated PDF is not byte-identical to the InDesign
8
+ output. InDesign produces ~100KB, the idml render produces ~2.2MB
9
+ (22x larger). The visual content is structurally similar but
10
+ rendered through a different font pipeline, font subsetting
11
+ strategy, image embedding path, and color management. Below is a
12
+ side-by-side audit.
13
+
14
+ ## File-level comparison (`sample-with-table-more`)
15
+
16
+ | Aspect | InDesign pages.pdf | idml (ours) |
17
+ |---|---|---|
18
+ | Size | 100,965 bytes | 2,196,913 bytes |
19
+ | Pages | 4 | 4 |
20
+ | PDF version | 1.4 | 1.4 |
21
+ | Embedded fonts | 1 (subsetted `MinionPro-Regular`) | 1 (full `MinionPro-BoldCn`) |
22
+ | Font stream size | 8,346 bytes (subsetted) | 232,680 bytes (full) |
23
+ | Image streams | 4 (small, downsampled) | 1 (huge, full-res) |
24
+ | Image stream sizes | 11,773 + 1,731 + 4,291 + ... | 1,937,556 (single 1.94MB JPEG) |
25
+ | `/OutputIntents` | embedded | NOT embedded (even with `--pdf-a`) |
26
+ | `/StructTreeRoot` | NOT emitted | only with `--tagged` |
27
+ | `/OCG` layers | NOT emitted | NOT emitted |
28
+ | `/Metadata` XMP | NOT emitted | emitted (XMP extracted from IDML) |
29
+ | OCG/InDesign structure | rich (`PieceInfo`, `PageUIDList`) | absent |
30
+ | Compression (FlateDecode) | yes | absent on most streams |
31
+
32
+ ## The 1.94MB bloat
33
+
34
+ `/Users/mulgogi/Downloads/2.png` is a 600×400 RGBA PNG (278KB on
35
+ disk, link-resource size 43,765 bytes per IDML metadata). The idml
36
+ renderer reads the file at the path IDML specifies and re-encodes
37
+ it through pdfrb's image pipeline as a 2048×2048 JPEG (1.94MB).
38
+
39
+ InDesign's PDF re-encodes at 300ppi: a 4.7"×3.1" image at 300ppi
40
+ is 1410×930, which JPEG-compresses to ~12KB. InDesign's source
41
+ asset (43KB) is downsampled then DCT-encoded at low quality for
42
+ embed. The idml render doesn't apply downsampling — it just
43
+ re-encodes the source file.
44
+
45
+ ## Font selection discrepancy
46
+
47
+ The idml render uses `FontSetup#register` which picks the first
48
+ non-missing font in `package.fonts.font_family[0]` — that's
49
+ `MinionPro-BoldCn` (the first font in "Minion Pro" family).
50
+
51
+ InDesign's PDF embeds `MinionPro-Regular` — that's because each
52
+ text run is rendered with the font declared in its
53
+ CharacterStyleRange#AppliedFont, which can differ per run.
54
+
55
+ The idml render's `StyleResolver::StyledRun` doesn't currently
56
+ carry `applied_font` (TODO: extract from CSR#AppliedFont). Once
57
+ extracted, the TextFrameRenderer can resolve per-run fonts via
58
+ `FontSetup#register_resolved` and register each in pdfrb.
59
+
60
+ ## Font subsetting
61
+
62
+ `Pdfrb::Font::TrueType::Subsetter` exists in pdfrb 0.5.0 and works
63
+ when called. Pipeline calls `writer.subset_fonts!` when
64
+ `subset_fonts: true`. The resulting file still embeds a ~233KB
65
+ font stream — investigation needed to determine why
66
+ subsetting isn't shrinking the file.
67
+
68
+ Likely cause: the `MinionPro-BoldCn` font we register isn't being
69
+ used to draw any text (no codepoints were used through it during
70
+ rendering), so subsetting doesn't apply. But the full font file is
71
+ still embedded because we registered it.
72
+
73
+ Fix: register only the font(s) that actually carry used codepoints.
74
+
75
+ ## Image handling
76
+
77
+ The idml render walks `<Image>` elements via `ImageCollector`,
78
+ reading the file at `LinkResourceURI` and re-encoding as JPEG.
79
+ `Image.compute_placement` uses the image's `ItemTransform` for
80
+ sizing.
81
+
82
+ InDesign's PDF embeds the image at downsampled resolution (300ppi)
83
+ and uses Smask for transparency. The idml render doesn't have an
84
+ Smask path.
85
+
86
+ ## Structure and metadata
87
+
88
+ InDesign's PDF embeds `PieceInfo`, `PageUIDList`,
89
+ `OriginalDocumentID`, `LastModified` etc. — these are
90
+ InDesign-specific structure for round-tripping. The idml render
91
+ doesn't emit any of this (it's not part of the PDF spec, but
92
+ IDML-aware tools can read it).
93
+
94
+ The idml render's PDF has `/Metadata` XMP (extracted from
95
+ `META-INF/metadata.xml`) — InDesign's reference PDF does not have
96
+ it. Adding XMP is a feature, not a bug.
97
+
98
+ ## Visual fidelity
99
+
100
+ - **Table layout**: After v0.4.8, the idml render uses
101
+ `Row#single_row_height` for vertical sizing. InDesign uses
102
+ this too. But InDesign also accounts for spanning cells, fixed
103
+ heights from row attributes, and additional CellStyle overrides.
104
+ The idml render doesn't model these yet (no fixtures with
105
+ spanning cells in the test suite).
106
+ - **Text fonts**: As noted, the idml render uses
107
+ `MinionPro-BoldCn` (heavy, wide) for all text. InDesign uses
108
+ `MinionPro-Regular` (the lighter body weight).
109
+ - **Images**: Not yet rendered with downsampling.
110
+ - **Layer/Swatch support**: Limited.
111
+
112
+ ## What to do about it
113
+
114
+ The idml render is correct in **structure** (table, fonts,
115
+ shapes) but **incomplete in fidelity** (subsetting, downsampling,
116
+ per-run font selection, layer/swatches).
117
+
118
+ For the test suite purpose, the key value is regression coverage:
119
+ the idml render now produces a valid PDF for the real fixture,
120
+ even if it doesn't match InDesign's output. Future work to close
121
+ the gap:
122
+
123
+ 1. **TODO 88: Per-run font resolution** — extract
124
+ CharacterStyleRange#AppliedFont in StyleResolver and pass
125
+ through to renderer.
126
+ 2. **Image downsampling** — when EffectivePpi > 72 and
127
+ `ImageExportResolution=Ppi300`, downsample the source image to
128
+ fit the rendered bounds at 300ppi before encoding as JPEG.
129
+ 3. **Oversized font detection** — only embed fonts that have used
130
+ codepoints (skip fully-unused fonts to avoid embedding
131
+ duplicates).
132
+ 4. **Table column widths** — derive from
133
+ `ColumnAttributes#ColumnWidth` rather than even division.
134
+
135
+ ## Verification
136
+
137
+ - `lib/idml/render/renderers/table_renderer.rb` — schema-faithful
138
+ Table rendering (v0.4.7).
139
+ - `lib/idml/render/renderers/text_frame_renderer.rb` — inline
140
+ Table discovery via `Story > PSR > CSR > Table` (v0.4.8).
141
+ - `spec/fixtures/sample-with-table-more/` — real IDML fixture.
142
+ - `spec/idml/fixtures/sample_with_table_more_spec.rb` — 17 specs.
@@ -0,0 +1,55 @@
1
+ # TODO PDF 90: Per-run font resolution
2
+
3
+ ## Status: PLANNED (TODO 89 audit follow-up)
4
+
5
+ ## Problem
6
+
7
+ `Idml::Render::FontSetup#register` picks the first non-missing font
8
+ in the document's `font_family` collection. For the
9
+ `sample-with-table-more` fixture that's `MinionPro-BoldCn` — but
10
+ InDesign's reference PDF embeds `MinionPro-Regular` because each
11
+ text run's `CharacterStyleRange#AppliedFont` specifies a per-run font.
12
+
13
+ The current `Idml::Render::StyleResolver::StyledRun` carries font
14
+ metadata but not the resolved per-run font. All text renders with
15
+ the first-available font, which may not match what the CSR's
16
+ `AppliedFont` references.
17
+
18
+ ## Plan
19
+
20
+ 1. **Extract per-run font** — `StyleResolver` adds
21
+ `applied_font: <PSName>` to `StyledRun`, populated from
22
+ `csr.applied_font` (a `CharacterStyle` reference, not a font
23
+ directly).
24
+
25
+ 2. **Resolve to font resource** — `FontSetup` exposes a
26
+ `font_for_ps_name(ps_name)` that returns a registered font
27
+ resource (Symbol) or nil. Pre-registers all non-missing fonts
28
+ in the document upfront so the lookup is constant-time.
29
+
30
+ 3. **Pass per-run font through the renderer** — the render path
31
+ resolves `run.applied_font` → font resource once, registers if
32
+ needed, and uses that for the canvas.text / text_rich /
33
+ text_lines calls.
34
+
35
+ 4. **Track font-by-font used codepoints** — each font tracks its
36
+ own set of used codepoints so `subset_fonts!` can produce
37
+ per-font subsets.
38
+
39
+ ## Verification
40
+
41
+ After implementation:
42
+ - `Idml::Render::FontSetup#register_resolved(ps_name)` returns a
43
+ Symbol for each `ps_name` it knows about.
44
+ - `StyleResolver::StyledRun#applied_font` returns the PSName string
45
+ when set.
46
+ - TextFrameRenderer resolves `run.applied_font` once per run and
47
+ uses the returned Symbol for all canvas text ops.
48
+ - subset_fonts! produces a smaller output for sample-with-table-more.
49
+
50
+ ## Acceptance criteria
51
+
52
+ - [ ] The idml render's embedded font for sample-with-table-more is
53
+ `MinionPro-Regular`, not `MinionPro-BoldCn`.
54
+ - [ ] Font stream size shrinks from 232KB to <50KB after subsetting.
55
+ - [ ] Spec asserts the correct font is selected per run.
@@ -0,0 +1,46 @@
1
+ # TODO PDF 91: Image downsampling for embed
2
+
3
+ ## Status: PLANNED (TODO 89 audit follow-up)
4
+
5
+ ## Problem
6
+
7
+ The idml render reads the image at the path IDML specifies
8
+ (`LinkResourceURI="file:..."`) and re-encodes it through pdfrb's
9
+ image pipeline as a JPEG. The current code does not downsample —
10
+ for `sample-with-table-more` the embedded 2.png (600×400 RGBA,
11
+ 278KB) is rendered into a 2048×2048 JPEG (1.94MB).
12
+
13
+ InDesign's reference PDF downsamples to the rendered bounds at the
14
+ export resolution (300ppi for the test fixture). The same image
15
+ becomes 1410×930 → ~12KB JPEG.
16
+
17
+ ## Plan
18
+
19
+ 1. **Compute render bounds** — `Image.compute_placement` already
20
+ returns the placement rect. The bounds define the output
21
+ dimensions in PDF points.
22
+ 2. **Compute target pixel dimensions** — convert placement width /
23
+ height in points to pixels at 300ppi (or the
24
+ `ImageExportResolution` attribute if present):
25
+ `target_w = (width / 72) * 300`, similarly for height.
26
+ 3. **Downsample before encoding** — if source dimensions exceed
27
+ target, resize using a simple bilinear algorithm. Otherwise
28
+ encode at source resolution.
29
+ 4. **Strip alpha if not needed** — for the test fixture the source
30
+ PNG has alpha but the rendered use is opaque. Strip alpha
31
+ before JPEG-encoding to save additional bytes.
32
+
33
+ ## Acceptance criteria
34
+
35
+ - [ ] Image stream size for sample-with-table-more <50KB (vs 1.94MB).
36
+ - [ ] Visual fidelity: rendered image is downsampled to 300ppi at
37
+ the placement bounds.
38
+ - [ ] Spec compares image stream sizes before/after downsampling.
39
+ - [ ] Test fixture with high-resolution image (1MB+) verifies
40
+ downsampling kicks in.
41
+
42
+ ## Implementation note
43
+
44
+ Pure-Ruby bilinear resize is simple but slow. A 2048×2048 → 600×400
45
+ resize would take ~200ms. For larger images, a C extension or
46
+ subprocess would be faster. For now, pure Ruby is acceptable.
data/lib/idml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Idml
4
- VERSION = "0.4.8"
4
+ VERSION = "0.4.9"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.8
4
+ version: 0.4.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -214,6 +214,9 @@ files:
214
214
  - TODO.pdf/85-per-text-range-hyperlinks.md
215
215
  - TODO.pdf/86-caching-performance.md
216
216
  - TODO.pdf/87-final-state-summary.md
217
+ - TODO.pdf/89-deep-audit-idml-vs-indesign.md
218
+ - TODO.pdf/90-per-run-font-resolution.md
219
+ - TODO.pdf/91-image-downsampling.md
217
220
  - TODO.pdf/README.md
218
221
  - exe/idml
219
222
  - idml.gemspec