idml 0.1.0 → 0.2.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 +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +12 -7
- data/TODO.pdf/51-remove-dead-code.md +9 -2
- data/TODO.pdf/53-tagged-pdf-accessibility.md +9 -1
- data/TODO.pdf/61-known-limitations.md +35 -0
- data/TODO.pdf/62-pdfrb-015-migration.md +44 -0
- data/TODO.pdf/63-replace-fontmetrics-with-pdfrb.md +31 -0
- data/idml.gemspec +1 -1
- data/lib/idml/cli.rb +12 -4
- data/lib/idml/render/gradient_resolver.rb +9 -8
- data/lib/idml/render/image.rb +5 -10
- data/lib/idml/render/pdfrb_writer.rb +46 -55
- data/lib/idml/render/pipeline.rb +4 -1
- data/lib/idml/render/spread_renderer.rb +7 -5
- data/lib/idml/render.rb +17 -24
- data/lib/idml/version.rb +1 -1
- metadata +6 -14
- data/TODO.pdf/56-text-engine-pdfrb-integration.md +0 -33
- data/TODO.pdf/57-image-clipping-pdfrb.md +0 -26
- data/TODO.pdf/58-bookmarks-pdfrb.md +0 -23
- data/lib/idml/render/color.rb +0 -69
- data/lib/idml/render/font_embedder.rb +0 -79
- data/lib/idml/render/path.rb +0 -80
- data/lib/idml/render/pdf_writer.rb +0 -426
- data/lib/idml/render/pdfrb_ext/clip.rb +0 -31
- data/lib/idml/render/pdfrb_ext/invoke_xobject.rb +0 -24
- data/lib/idml/render/pdfrb_ext.rb +0 -13
- data/lib/idml/render/text.rb +0 -91
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# TODO PDF 56: Text engine full integration with pdfrb Canvas
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Re-integrate the text engine (Shaper → LineBreaker → Justifier →
|
|
6
|
-
VerticalLayout) into TextFrameRenderer using pdfrb Canvas. Each line
|
|
7
|
-
of shaped/broken text is emitted as a separate `canvas.text` call
|
|
8
|
-
at the computed baseline position.
|
|
9
|
-
|
|
10
|
-
## Status: DONE
|
|
11
|
-
|
|
12
|
-
## What was implemented
|
|
13
|
-
|
|
14
|
-
TextFrameRenderer has two rendering paths:
|
|
15
|
-
1. **engine_render** (when FontMetrics available): shapes text via
|
|
16
|
-
Shaper, breaks lines via LineBreaker at frame width, positions
|
|
17
|
-
each line at decreasing baseline_y, emits `canvas.text` per line.
|
|
18
|
-
Lines beyond frame bottom are skipped.
|
|
19
|
-
2. **simple_render** (fallback): one `canvas.text` per styled run at
|
|
20
|
-
sequential y positions.
|
|
21
|
-
|
|
22
|
-
The FontMetrics comes from `context.font_resolver.resolve(family_name:)`.
|
|
23
|
-
On systems where the default font is available as .ttf (not .ttc), the
|
|
24
|
-
text engine provides proper word-wrap.
|
|
25
|
-
|
|
26
|
-
## Acceptance criteria
|
|
27
|
-
|
|
28
|
-
- [x] TextFrameRenderer tries FontResolver for metrics.
|
|
29
|
-
- [x] When metrics available: Shaper → LineBreaker → per-line canvas.text.
|
|
30
|
-
- [x] When metrics unavailable: simple one-text-per-run fallback.
|
|
31
|
-
- [x] Frame bounds respected (lines beyond frame bottom skipped).
|
|
32
|
-
- [x] Chain head check prevents duplicate text in overflow frames.
|
|
33
|
-
- [x] Text frame insets subtracted from frame box.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# TODO PDF 57: Image clipping via pdfrb Canvas
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Clip placed images to their parent Rectangle's bounds using PDF clip
|
|
6
|
-
paths. Prevents image overflow beyond the containing frame.
|
|
7
|
-
|
|
8
|
-
## Status: DONE
|
|
9
|
-
|
|
10
|
-
## What was implemented
|
|
11
|
-
|
|
12
|
-
- `PdfrbExt::Clip` (PDF `W` operator) and `PdfrbExt::EndPath` (PDF `n`
|
|
13
|
-
operator) registered as pdfrb Content::Operator subclasses.
|
|
14
|
-
- `SpreadRenderer#apply_image_clip` emits a clip path from the parent
|
|
15
|
-
Rectangle's geometric_bounds before drawing the image.
|
|
16
|
-
- `Pipeline#parent_clip_box` computes the clip rectangle from the
|
|
17
|
-
parent page item's bounds via Geometry helpers.
|
|
18
|
-
- Image refs now carry a `clip_box` field; when present, the SpreadRenderer
|
|
19
|
-
clips before drawing.
|
|
20
|
-
|
|
21
|
-
## Acceptance criteria
|
|
22
|
-
|
|
23
|
-
- [x] PdfrbExt::Clip and EndPath operators registered.
|
|
24
|
-
- [x] SpreadRenderer applies clip path when clip_box is present.
|
|
25
|
-
- [x] Pipeline computes clip_box from parent geometric_bounds.
|
|
26
|
-
- [x] All tests pass.
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# TODO PDF 58: PDF bookmarks via pdfrb outline API
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Generate PDF bookmark (outline) entries using pdfrb's outline API.
|
|
6
|
-
Each bookmark links to a page destination.
|
|
7
|
-
|
|
8
|
-
## Status: DONE
|
|
9
|
-
|
|
10
|
-
## What was implemented
|
|
11
|
-
|
|
12
|
-
- `PdfrbWriter#add_bookmark(title, page_index)` delegates to
|
|
13
|
-
`Pdfrb::Document#outline.add(title, dest: page)`.
|
|
14
|
-
- Available for Pipeline consumers to add bookmarks for headings or
|
|
15
|
-
sections.
|
|
16
|
-
- `PdfrbExt::Clip` and `PdfrbExt::EndPath` also added for image clipping
|
|
17
|
-
(TODO 57).
|
|
18
|
-
|
|
19
|
-
## Acceptance criteria
|
|
20
|
-
|
|
21
|
-
- [x] PdfrbWriter#add_bookmark delegates to pdfrb outline API.
|
|
22
|
-
- [x] Anti-pattern spec passes (no new violations).
|
|
23
|
-
- [x] All tests pass.
|
data/lib/idml/render/color.rb
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# DEPRECATED: This module is superseded by ColorHelper + Canvas
|
|
4
|
-
# fill_color/stroke_color methods. Kept for backward compatibility.
|
|
5
|
-
|
|
6
|
-
module Idml
|
|
7
|
-
module Render
|
|
8
|
-
# Converts IDML color values to PDF content-stream operators.
|
|
9
|
-
module Color
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
# RGB fill: `r g b rg`. Values 0.0–1.0.
|
|
13
|
-
def fill_rgb(r, g, b)
|
|
14
|
-
format("%<r>.4f %<g>.4f %<b>.4f rg", r: r, g: g, b: b)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# RGB stroke: `r g b RG`.
|
|
18
|
-
def stroke_rgb(r, g, b)
|
|
19
|
-
format("%<r>.4f %<g>.4f %<b>.4f RG", r: r, g: g, b: b)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# CMYK fill: `c m y k k`.
|
|
23
|
-
def fill_cmyk(c, m, y, k)
|
|
24
|
-
format("%<c>.4f %<m>.4f %<y>.4f %<k>.4f k", c: c, m: m, y: y, k: k)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# CMYK stroke: `c m y k K`.
|
|
28
|
-
def stroke_cmyk(c, m, y, k)
|
|
29
|
-
format("%<c>.4f %<m>.4f %<y>.4f %<k>.4f K", c: c, m: m, y: y, k: k)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Parse IDML color space value (space-separated 0–255 or 0–100
|
|
33
|
-
# depending on model) to normalized 0.0–1.0.
|
|
34
|
-
def normalize_channel(value, max = 255)
|
|
35
|
-
value.to_f / max
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Black fill (common default).
|
|
39
|
-
def black_fill
|
|
40
|
-
"0 g"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# White fill.
|
|
44
|
-
def white_fill
|
|
45
|
-
"1 g"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Dispatch: accepts a color hash ({ model:, r:, g:, b: } or
|
|
49
|
-
# { model:, c:, m:, y:, k: }) and returns the appropriate
|
|
50
|
-
# fill operator string.
|
|
51
|
-
def fill_op(color)
|
|
52
|
-
case color[:model]
|
|
53
|
-
when :rgb then fill_rgb(color[:r], color[:g], color[:b])
|
|
54
|
-
when :cmyk then fill_cmyk(color[:c], color[:m], color[:y], color[:k])
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Dispatch: accepts a color hash and returns the appropriate
|
|
59
|
-
# stroke operator string.
|
|
60
|
-
def stroke_op(color)
|
|
61
|
-
case color[:model]
|
|
62
|
-
when :rgb then stroke_rgb(color[:r], color[:g], color[:b])
|
|
63
|
-
when :cmyk then stroke_cmyk(color[:c], color[:m], color[:y],
|
|
64
|
-
color[:k])
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# DEPRECATED: This module is superseded by PdfrbWriter.register_font.
|
|
4
|
-
# Font embedding is now handled by pdfrb (Pdfrb::Document#fonts.add).
|
|
5
|
-
|
|
6
|
-
module Idml
|
|
7
|
-
module Render
|
|
8
|
-
# Generates PDF font structures for embedded TrueType fonts.
|
|
9
|
-
# Produces the Widths array (WinAnsiEncoding 32–255), FontDescriptor
|
|
10
|
-
# metadata, and FontFile2 binary embedding.
|
|
11
|
-
module FontEmbedder
|
|
12
|
-
FIRST_CHAR = 32
|
|
13
|
-
LAST_CHAR = 255
|
|
14
|
-
|
|
15
|
-
# PDF WinAnsiEncoding = Code Page 1252. Bytes 0x80-0x9F differ
|
|
16
|
-
# from ISO-8859-1 and map to specific Unicode codepoints.
|
|
17
|
-
CP1252_SPECIAL = {
|
|
18
|
-
0x80 => 0x20AC, 0x82 => 0x201A, 0x83 => 0x0192, 0x84 => 0x201E,
|
|
19
|
-
0x85 => 0x2026, 0x86 => 0x2020, 0x87 => 0x2021, 0x88 => 0x02C6,
|
|
20
|
-
0x89 => 0x2030, 0x8A => 0x0160, 0x8B => 0x2039, 0x8C => 0x0152,
|
|
21
|
-
0x8E => 0x017D, 0x91 => 0x2018, 0x92 => 0x2019, 0x93 => 0x201C,
|
|
22
|
-
0x94 => 0x201D, 0x95 => 0x2022, 0x96 => 0x2013, 0x97 => 0x2014,
|
|
23
|
-
0x98 => 0x02DC, 0x99 => 0x2122, 0x9A => 0x0161, 0x9B => 0x203A,
|
|
24
|
-
0x9C => 0x0153, 0x9E => 0x017E, 0x9F => 0x0178
|
|
25
|
-
}.freeze
|
|
26
|
-
|
|
27
|
-
private_constant :CP1252_SPECIAL
|
|
28
|
-
|
|
29
|
-
module_function
|
|
30
|
-
|
|
31
|
-
# Widths array for WinAnsiEncoding (chars 32–255), in PDF text
|
|
32
|
-
# units (1000 units per em).
|
|
33
|
-
def widths_array(font_metrics)
|
|
34
|
-
(FIRST_CHAR..LAST_CHAR).map do |byte|
|
|
35
|
-
cp = winansi_to_unicode(byte)
|
|
36
|
-
raw = font_metrics.glyph_width(cp)
|
|
37
|
-
scale_to_text_units(raw, font_metrics.units_per_em)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Map a WinAnsi (CP1252) byte value to its Unicode codepoint.
|
|
42
|
-
def winansi_to_unicode(byte)
|
|
43
|
-
CP1252_SPECIAL.fetch(byte, byte)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def scale_to_text_units(font_units, upem)
|
|
47
|
-
(font_units.to_f * 1000 / upem).round
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# FontDescriptor metadata hash.
|
|
51
|
-
def descriptor(font_metrics)
|
|
52
|
-
upem = font_metrics.units_per_em
|
|
53
|
-
scale = 1000.0 / upem
|
|
54
|
-
ascent = font_metrics.ascent.to_f
|
|
55
|
-
descent = font_metrics.descent.to_f
|
|
56
|
-
{
|
|
57
|
-
font_name: font_metrics.postscript_name,
|
|
58
|
-
flags: 32,
|
|
59
|
-
font_bbox: [
|
|
60
|
-
(descent * scale).round,
|
|
61
|
-
0,
|
|
62
|
-
1000,
|
|
63
|
-
(ascent * scale).round,
|
|
64
|
-
],
|
|
65
|
-
italic_angle: 0,
|
|
66
|
-
ascent: (ascent * scale).round,
|
|
67
|
-
descent: (descent * scale).round,
|
|
68
|
-
cap_height: (ascent * scale * 0.7).round,
|
|
69
|
-
stem_v: 80,
|
|
70
|
-
}
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Read raw font binary data from the FontMetrics source path.
|
|
74
|
-
def raw_font_data(font_metrics)
|
|
75
|
-
File.binread(font_metrics.path)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
data/lib/idml/render/path.rb
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# DEPRECATED: This module is superseded by Pdfrb::Content::Canvas
|
|
4
|
-
# drawing methods (rectangle, stroke, fill, etc.).
|
|
5
|
-
|
|
6
|
-
module Idml
|
|
7
|
-
module Render
|
|
8
|
-
# Converts IDML geometric page items to PDF path operators.
|
|
9
|
-
module Path
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
# Rectangle → `x y w h re`
|
|
13
|
-
def rectangle(x:, y:, width:, height:)
|
|
14
|
-
format("%<x>.2f %<y>.2f %<w>.2f %<h>.2f re", x: x, y: y, w: width,
|
|
15
|
-
h: height)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Save graphics state
|
|
19
|
-
def save_state
|
|
20
|
-
"q"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Restore graphics state
|
|
24
|
-
def restore_state
|
|
25
|
-
"Q"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Transformation matrix: `a b c d e f cm`
|
|
29
|
-
def transform(a:, b:, c:, d:, e:, f:)
|
|
30
|
-
format("%<a>.4f %<b>.4f %<c>.4f %<d>.4f %<e>.2f %<f>.2f cm",
|
|
31
|
-
a: a, b: b, c: c, d: d, e: e, f: f)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Fill the current path (nonzero winding).
|
|
35
|
-
def fill
|
|
36
|
-
"f"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Set clip path (nonzero winding). Must be followed by `n` (no-op).
|
|
40
|
-
def clip
|
|
41
|
-
"W n"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Set clip path (even-odd).
|
|
45
|
-
def eo_clip
|
|
46
|
-
"W* n"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Stroke the current path.
|
|
50
|
-
def stroke
|
|
51
|
-
"S"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Set stroke line width: `w w`
|
|
55
|
-
def stroke_width(weight)
|
|
56
|
-
format("%<w>.2f w", w: weight)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Close, fill, and stroke the current path.
|
|
60
|
-
def fill_stroke
|
|
61
|
-
"B"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Close the current path.
|
|
65
|
-
def close
|
|
66
|
-
"h"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Move to a point: `x y m`
|
|
70
|
-
def move_to(x, y)
|
|
71
|
-
format("%<x>.2f %<y>.2f m", x: x, y: y)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Line to a point: `x y l`
|
|
75
|
-
def line_to(x, y)
|
|
76
|
-
format("%<x>.2f %<y>.2f l", x: x, y: y)
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|