Class: Idml::Render::TextWrapResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/idml/render/text_wrap_resolver.rb

Overview

Resolves text-wrap contours from page items that declare TextWrapPreference. For BoundingBox mode (the common case), the contour is the item's geometric bounds expanded by the wrap offsets. TextFrameRenderer queries this resolver per text line to compute the effective wrap width — lines that overlap a contour get their width reduced by the overlap.

Supported: BoundingBox mode (rectangle intersection) with TextWrapSide awareness (LeftSide / RightSide / LargestArea pick the side text flows on; BothSides and the spine variants narrow by the full overlap). Shape contours (Contour mode) narrow by polygon overlap without side awareness. Unsupported: Inverse mode's side behavior.

Defined Under Namespace

Classes: Contour

Constant Summary collapse

OFFSET_ATTRS =
i[
  offset_top offset_left offset_bottom offset_right
].freeze
JUMP_MODES =

The wrap shape for an item: a bounding-box rectangle (BoundingBoxTextWrap — the legacy "BoundingBox" spelling is also accepted from early synthetic fixtures) or a flattened PathGeometry polygon (Contour mode). JumpObject moves text below the object; NextColumn approximates as the same jump-below in a single-column frame (true column jumping needs chain integration, documented in TODO.pdf/61).

%w[JumpObjectTextWrap NextColumnTextWrap].freeze
WRAPPABLE_TYPES =

Page item types that can declare TextWrapPreference per the Spread schema. Used to guard the attribute read — querying non-shape items (Page, Link) would raise NoMethodError.

[
  Idml::Elements::Rectangle,
  Idml::Elements::Oval,
  Idml::Elements::Polygon,
  Idml::Elements::GraphicLine,
  Idml::Elements::Path,
  Idml::Elements::Group,
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(contours) ⇒ TextWrapResolver

Returns a new instance of TextWrapResolver.



38
39
40
# File 'lib/idml/render/text_wrap_resolver.rb', line 38

def initialize(contours)
  @contours = contours
end

Instance Attribute Details

#contoursObject (readonly)

Returns the value of attribute contours.



42
43
44
# File 'lib/idml/render/text_wrap_resolver.rb', line 42

def contours
  @contours
end

Class Method Details

.build(spread, page_height: 792) ⇒ Object



29
30
31
32
33
34
35
36
# File 'lib/idml/render/text_wrap_resolver.rb', line 29

def self.build(spread, page_height: 792)
  contours = []
  spread.each_page_item do |item|
    contour = contour_for(item, page_height)
    contours << contour if contour
  end
  new(contours)
end

Instance Method Details

#jump_contour_bottom(line_y, line_height, frame_x, frame_right) ⇒ Object

Bottom edge of the lowest jump contour overlapping the given band — where text resumes below a JumpObject. nil when no jump contour blocks the band.



117
118
119
120
121
122
123
124
125
126
# File 'lib/idml/render/text_wrap_resolver.rb', line 117

def jump_contour_bottom(line_y, line_height, frame_x, frame_right)
  bottoms = @contours.filter_map do |contour|
    next unless skip_contour?(contour)
    next unless y_overlap?(contour, line_y, line_height) &&
      x_overlap?(contour, frame_x, frame_right)

    contour.y
  end
  bottoms.min
end

#next_column_block?(line_y, line_height, frame_x, frame_right) ⇒ Boolean

True when a NextColumn contour blocks the given band: in a multi-column frame the renderer abandons the column and the chain resumes in the next one.

Returns:

  • (Boolean)


139
140
141
142
143
144
145
# File 'lib/idml/render/text_wrap_resolver.rb', line 139

def next_column_block?(line_y, line_height, frame_x, frame_right)
  @contours.any? do |contour|
    box_contour?(contour) && contour.next_column &&
      y_overlap?(contour, line_y, line_height) &&
      x_overlap?(contour, frame_x, frame_right)
  end
end

#wrap_adjustment(line_y, line_height, frame_x, frame_right) ⇒ Object

Side-aware adjustment for a text line: returns [width_reduction, x_shift]. LeftSide keeps text left of the contour (reduce, no shift); RightSide moves text past the contour's right edge (reduce + shift); LargestArea picks the roomier side; BothSides and the spine variants (which need binding-side context) reduce by the full overlap. Multiple contours combine by max — the per-run approximation.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/idml/render/text_wrap_resolver.rb', line 51

def wrap_adjustment(line_y, line_height, frame_x, frame_right)
  reduction = 0.0
  shift = 0.0
  @contours.each do |contour|
    unless contour.is_a?(WrapContour::Shape)
      r, s = box_adjustment(contour, line_y, line_height,
                            frame_x, frame_right)
      reduction = [reduction, r].max
      shift = [shift, s].max
      next
    end

    width = WrapContour.overlap_width(contour, line_y,
                                      line_height, frame_x,
                                      frame_right)
    if contour.inverse
      reduction = [reduction,
                   (frame_right - frame_x) - width].max
      next
    end

    # Side-aware shapes (TODO 147): the side dispatch uses
    # the polygon's bounding box; narrowing itself keeps the
    # exact polygon overlap for BothSides.
    bbox_left, _bottom, bbox_right, = WrapContour.bounding_box(
      contour,
    )
    r, s = interval_side_adjustment(contour.side, bbox_left,
                                    bbox_right, width,
                                    frame_x, frame_right)
    reduction = [reduction, r].max
    shift = [shift, s].max
  end
  [reduction, shift]
end

#x_overlap?(contour, frame_x, frame_right) ⇒ Boolean

Returns:

  • (Boolean)


269
270
271
# File 'lib/idml/render/text_wrap_resolver.rb', line 269

def x_overlap?(contour, frame_x, frame_right)
  contour.x < frame_right && contour.x + contour.width > frame_x
end

#x_overlap_width(contour, frame_x, frame_right) ⇒ Object



273
274
275
276
277
# File 'lib/idml/render/text_wrap_resolver.rb', line 273

def x_overlap_width(contour, frame_x, frame_right)
  left = [contour.x, frame_x].max
  right = [contour.x + contour.width, frame_right].min
  [right - left, 0].max
end

#y_overlap?(contour, line_y, line_height) ⇒ Boolean

Returns:

  • (Boolean)


261
262
263
264
265
266
267
# File 'lib/idml/render/text_wrap_resolver.rb', line 261

def y_overlap?(contour, line_y, line_height)
  line_top = line_y + line_height
  line_bottom = line_y
  contour_top = contour.y + contour.height
  contour_bottom = contour.y
  line_top > contour_bottom && line_bottom < contour_top
end