idml 0.5.0 → 0.5.1

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: '09c5f631233897e7f1e18519b1031c1c87092a1e3ddffdc353bf2e121c47b15b'
4
- data.tar.gz: 8c0b965da67d83e30f3b7002be6bbabe1dffec7d1a3557979fe4cdfbce99dd54
3
+ metadata.gz: d4b32bcaed6562461191096ac7bbcd0ff8a14c151056bdbf122c439a711dc869
4
+ data.tar.gz: 30cff3491f9588716cfb676b00509f728c7c2af21b0efe9cb5a6ac4f2b77fe0b
5
5
  SHA512:
6
- metadata.gz: 7675a8a5b799b1a2b420f74b26faec4f6630b55cbf7ca831201085f99947d9ee31d1380fe5a12039427e643988ec84228738fc92dd9d925423aa36822815a774
7
- data.tar.gz: 266b6c589b715d727460eba5064dde878f104b079e3da2ae7a0b31c911492cd88d75d18946ff3536d358c12359bf4b4069592dba7ac0c4b5eb42a1dbebde0544
6
+ metadata.gz: a97de15a173bca917a1e10aa4fc32eb553b2ee2bd88fc33f49953437513e923b90acdaa02a734b74bc3d51493ba464aadbfbb51f86ede65369c0cf2a7a779911
7
+ data.tar.gz: 55448f8fb8d465fe0fbc2d078b4530ef80843721055856717788d7a77f6984f5e20a44dcaa41a8e2741a66b17b616b174f322ed0a770b3236c8617b6545054c4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- idml (0.5.0)
4
+ idml (0.5.1)
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.5.0)
186
+ idml (0.5.1)
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
@@ -1,22 +1,91 @@
1
1
  # TODO PDF 89: Deep audit — idml-generated PDF vs InDesign PDF
2
2
 
3
- ## Status: PARTIAL — font selection fixed; image bloat remains
3
+ ## Status: PARTIAL — font + lossless compression fixed; no downsampling
4
4
 
5
5
  ## TL;DR
6
6
 
7
- **No** — the generated PDF is still not identical to InDesign's
8
- output, but the **font selection gap is closed** as of v0.5.0.
7
+ **No** — the generated PDF is still not byte-identical to InDesign's
8
+ output. Two of three substantive gaps closed without data loss:
9
+
10
+ 1. **Font selection (closed, v0.5.0)** — FontSetup now prefers the
11
+ family's Regular/Normal/Book/Roman variant over first-in-family.
12
+ Sample-with-table-more now embeds `MinionPro-Regular` (matching
13
+ InDesign).
14
+ 2. **Lossless FlateDecode compression (closed, v0.5.1)** — pdfrb
15
+ writes FlateDecode-compressed content streams, XRef streams, and
16
+ object streams when `compress: true`. Saves ~96KB (4%) on the
17
+ sample-with-table-more fixture without any data loss.
18
+ 3. **Image downsampling** — **not pursued**. The user requirement is
19
+ **no data loss**. InDesign's 100KB output downsamples the 1.94MB
20
+ source image (DCTDecode) to ~12KB at 300ppi; we keep the original
21
+ resolution. The 1.94MB stream dominates file size and cannot be
22
+ reduced without losing image data.
9
23
 
10
- After the font fix (FontSetup now prefers Regular weight over
11
- first-in-family):
12
- - Font: `MinionPro-Regular` (matches InDesign) — was
13
- `MinionPro-BoldCn`.
14
- - File size: still ~2.2MB (the image bloat remains).
24
+ ## File-level comparison (`sample-with-table-more`)
25
+
26
+ | Aspect | InDesign pages.pdf | idml (default) | idml (`compress: true`) |
27
+ |---|---|---|---|
28
+ | Size | 100,965 bytes | 2,195,575 bytes | **2,099,493 bytes** |
29
+ | Pages | 4 | 4 ✓ | 4 ✓ |
30
+ | PDF version | 1.4 | 1.4 ✓ | 1.4 ✓ |
31
+ | Embedded font | `MinionPro-Regular` | `MinionPro-Regular` ✓ | `MinionPro-Regular` ✓ |
32
+ | Font stream size | ~8KB (subsetted) | ~232KB (full) | ~231KB (full) |
33
+ | FlateDecode streams | 24 | 0 | 8 |
34
+ | Image streams | 4 (downsampled ~12KB each) | 1 (raw 1.94MB) | 1 (raw 1.94MB) |
35
+
36
+ ## Why we can't match InDesign's 100KB
37
+
38
+ The 1.94MB JPEG image (`GenAIImage_53e34e93-…jpeg` referenced via
39
+ `LinkResourceURI` in Spread_ud1) is the dominant file-size cost. It
40
+ is embedded raw via DCTDecode because:
41
+
42
+ - The user requirement is **no data loss**.
43
+ - InDesign's 12KB version downsamples to 300ppi at the placement
44
+ bounds — that IS data loss.
45
+ - Keeping the image at full resolution = 1.94MB minimum.
46
+
47
+ Without downsampling, the file size floor is the source image size
48
+ plus the font stream plus small overhead:
49
+ 1,937,556 (image) + 231,312 (full font) + ~20KB (other) = ~2.2MB.
50
+ With `compress: true` we shave ~96KB through FlateDecode on the
51
+ non-image streams.
52
+
53
+ ## What we DO loselessly (besides image embedding)
54
+
55
+ - **FlateDecode** on content streams, ObjStms, and metadata streams.
56
+ - **XRef stream** (`use_xref_stream: true`) — compact xref table.
57
+ - **Object stream packing** (`pack_object_streams: true`) — packs
58
+ small indirect objects into a compressed ObjStm.
59
+
60
+ ## What we don't do (lossy — not enabled)
61
+
62
+ - **Image downsampling** at 300ppi (TODO 91 was re-classified as
63
+ REJECTED — user explicitly forbids data loss).
64
+ - **JPEG quality reduction** for embedded images.
65
+ - **Font subsetting to fewer glyphs than used** (lossless subsetting
66
+ IS done by pdfrb — removes only unused glyphs).
67
+
68
+ ## Future audit work
69
+
70
+ - **Stream-by-stream comparison** with `qpdf --qdf` to decompose both
71
+ PDFs into canonical form and diff.
72
+ - **Font subsetting investigation** — pdfrb's `subset_fonts!` is
73
+ called but the font stream is still 232KB. The likely cause is
74
+ that no codepoints are recorded as "used" for the registered font
75
+ (the renderer uses a default font that doesn't get text drawn
76
+ through it). Future fix: register the font actually used to draw
77
+ text, so `used_codepoints` populates correctly.
78
+
79
+ ## Verification
15
80
 
16
- The remaining 88% of the file size is the embedded GenAI JPEG
17
- (1,937,556 bytes embedded raw via DCTDecode). InDesign downsamples
18
- to ~12KB. Closing this gap requires pure-Ruby JPEG decode + resize
19
- + re-encode (TODO 91).
81
+ - `lib/idml/render/pdfrb_writer.rb:25` `LOSSLESS_WRITER_OPTIONS`
82
+ constant; opt-in via `compress: true`.
83
+ - `lib/idml/render/pipeline.rb` `compress:` keyword threaded
84
+ through `PdfrbWriter.new(compress: ...)`.
85
+ - `lib/idml/render.rb` — `Render.render(compress: false)` default.
86
+ - `lib/idml/cli.rb` — `--compress` CLI flag.
87
+ - `spec/idml/fixtures/sample_with_table_more_spec.rb:197` — spec
88
+ verifies compressed output is smaller and contains FlateDecode.
20
89
 
21
90
  ## File-level comparison (`sample-with-table-more`)
22
91
 
@@ -1,6 +1,40 @@
1
1
  # TODO PDF 91: Image downsampling for embed
2
2
 
3
- ## Status: PLANNED (TODO 89 audit follow-up)
3
+ ## Status: REJECTED violates "no data loss" requirement
4
+
5
+ The original proposal was to downsample source images to 300ppi at
6
+ the placement bounds before encoding as JPEG. This **loses image
7
+ data**: the output pixels are not the same as the source pixels.
8
+
9
+ The user has explicitly forbidden any data loss: "we don't want
10
+ downsampling, we cannot lose data."
11
+
12
+ Instead, the idml render keeps source images at full resolution
13
+ (embedded raw via DCTDecode for JPEG, FlateDecode for PNG). The
14
+ file size is dominated by the source image size, which is
15
+ unavoidable without data loss.
16
+
17
+ For users who want smaller PDFs and accept image data loss, the
18
+ recommended workflow is:
19
+
20
+ 1. Pre-process the source images before importing into InDesign
21
+ (resize in Photoshop, etc.).
22
+ 2. Export the IDML with the already-small images.
23
+ 3. Render via `idml render --compress` for lossless FlateDecode on
24
+ non-image streams.
25
+
26
+ ## What is preserved
27
+
28
+ - **Image fidelity**: source pixels are embedded bit-for-bit.
29
+ - **Font data**: only unused glyphs are dropped (lossless subsetting
30
+ via pdfrb's `TrueType::Subsetter`).
31
+ - **Structure**: tagged PDF, XMP, ICC output intent — all lossless.
32
+
33
+ ## Acceptance criteria
34
+
35
+ - [x] No image downsampling code introduced.
36
+ - [x] Source image bytes are preserved verbatim in the PDF stream.
37
+ - [x] Lossless FlateDecode compression available via `compress: true`.
4
38
 
5
39
  ## Problem
6
40
 
data/lib/idml/cli.rb CHANGED
@@ -69,6 +69,8 @@ module Idml
69
69
  desc: "Produce tagged PDF (PDF/UA)"
70
70
  method_option :no_subset, type: :boolean, default: false,
71
71
  desc: "Skip font subsetting (larger PDF)"
72
+ method_option :compress, type: :boolean, default: false,
73
+ desc: "Apply lossless FlateDecode compression"
72
74
  method_option :verbose, aliases: "-v", type: :boolean, default: false,
73
75
  desc: "Print progress"
74
76
  def render(path)
@@ -100,6 +102,7 @@ module Idml
100
102
  compliance: options[:pdf_a] ? :pdfa2a : nil,
101
103
  tagged: options[:tagged],
102
104
  subset_fonts: !options[:no_subset],
105
+ compress: options[:compress],
103
106
  }
104
107
  end
105
108
 
@@ -23,8 +23,27 @@ module Idml
23
23
  ModDate: :moddate=,
24
24
  }.freeze
25
25
 
26
- def initialize
27
- @document = Pdfrb::Document.new
26
+ # Lossless writer options applied to the underlying pdfrb
27
+ # Document. Each entry maps a pdfrb config key to its value.
28
+ # Compression (`FlateDecode`), XRef streams, and object-stream
29
+ # packing are all lossless and produce smaller PDFs without
30
+ # throwing away any data — no image downsampling, no font
31
+ # information loss.
32
+ #
33
+ # Compression is **opt-in** because enabling it makes
34
+ # stream-level assertions in specs harder (strings move into
35
+ # compressed ObjStms). Set `compress: true` on the writer or
36
+ # pipeline to enable.
37
+ COMPRESSED_OPTIONS = {
38
+ "writer.compress_streams" => true,
39
+ "writer.compress_min_size" => 64,
40
+ "writer.use_xref_stream" => true,
41
+ "writer.pack_object_streams" => true,
42
+ }.freeze
43
+
44
+ def initialize(compress: false)
45
+ config = compress ? COMPRESSED_OPTIONS : {}
46
+ @document = Pdfrb::Document.new(config: config)
28
47
  @image_cache = {}
29
48
  end
30
49
 
@@ -17,17 +17,19 @@ module Idml
17
17
  DEFAULT_HEIGHT = 792
18
18
 
19
19
  def initialize(package, output_path, font_search_paths = nil,
20
- compliance: nil, tagged: false, subset_fonts: true)
20
+ compliance: nil, tagged: false, subset_fonts: true,
21
+ compress: false)
21
22
  @package = package
22
23
  @output_path = output_path
23
24
  @font_search_paths = font_search_paths
24
25
  @compliance = compliance
25
26
  @tagged = tagged
26
27
  @subset_fonts = subset_fonts
28
+ @compress = compress
27
29
  end
28
30
 
29
31
  def call
30
- writer = PdfrbWriter.new
32
+ writer = PdfrbWriter.new(compress: @compress)
31
33
  metadata = MetadataBuilder.new(@package).build
32
34
  writer.set_info(metadata)
33
35
  writer.enable_tagged if @tagged
data/lib/idml/render.rb CHANGED
@@ -40,10 +40,10 @@ module Idml
40
40
  # `package:` and `to:` are keyword-only and optional.
41
41
  # rubocop:disable Metrics/ParameterLists
42
42
  def self.render(package:, to:, font_search_paths: nil, compliance: nil,
43
- tagged: false, subset_fonts: true)
43
+ tagged: false, subset_fonts: true, compress: false)
44
44
  Pipeline.new(package, to, font_search_paths,
45
45
  compliance: compliance, tagged: tagged,
46
- subset_fonts: subset_fonts).call
46
+ subset_fonts: subset_fonts, compress: compress).call
47
47
  end
48
48
  # rubocop:enable Metrics/ParameterLists
49
49
  end
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.5.0"
4
+ VERSION = "0.5.1"
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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose