/src/calibre/ebooks/pdf/render/gradients.py

https://github.com/norbusan/calibre-debian
Python | 155 lines | 120 code | 30 blank | 5 comment | 25 complexity | 729b9155af6d76a7b22f576cabe37653 MD5 | raw file
  1. #!/usr/bin/env python
  2. # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
  3. __license__ = 'GPL v3'
  4. __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
  5. __docformat__ = 'restructuredtext en'
  6. import sys, copy
  7. from polyglot.builtins import map, range
  8. from collections import namedtuple
  9. from PyQt5.Qt import QLinearGradient, QPointF
  10. try:
  11. from PyQt5 import sip
  12. except ImportError:
  13. import sip
  14. from calibre.ebooks.pdf.render.common import Name, Array, Dictionary
  15. Stop = namedtuple('Stop', 't color')
  16. class LinearGradientPattern(Dictionary):
  17. def __init__(self, brush, matrix, pdf, pixel_page_width, pixel_page_height):
  18. self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(),
  19. matrix.dx(), matrix.dy())
  20. gradient = sip.cast(brush.gradient(), QLinearGradient)
  21. start, stop, stops = self.spread_gradient(gradient, pixel_page_width,
  22. pixel_page_height, matrix)
  23. # TODO: Handle colors with different opacities
  24. self.const_opacity = stops[0].color[-1]
  25. funcs = Array()
  26. bounds = Array()
  27. encode = Array()
  28. for i, current_stop in enumerate(stops):
  29. if i < len(stops) - 1:
  30. next_stop = stops[i+1]
  31. func = Dictionary({
  32. 'FunctionType': 2,
  33. 'Domain': Array([0, 1]),
  34. 'C0': Array(current_stop.color[:3]),
  35. 'C1': Array(next_stop.color[:3]),
  36. 'N': 1,
  37. })
  38. funcs.append(func)
  39. encode.extend((0, 1))
  40. if i+1 < len(stops) - 1:
  41. bounds.append(next_stop.t)
  42. func = Dictionary({
  43. 'FunctionType': 3,
  44. 'Domain': Array([stops[0].t, stops[-1].t]),
  45. 'Functions': funcs,
  46. 'Bounds': bounds,
  47. 'Encode': encode,
  48. })
  49. shader = Dictionary({
  50. 'ShadingType': 2,
  51. 'ColorSpace': Name('DeviceRGB'),
  52. 'AntiAlias': True,
  53. 'Coords': Array([start.x(), start.y(), stop.x(), stop.y()]),
  54. 'Function': func,
  55. 'Extend': Array([True, True]),
  56. })
  57. Dictionary.__init__(self, {
  58. 'Type': Name('Pattern'),
  59. 'PatternType': 2,
  60. 'Shading': shader,
  61. 'Matrix': Array(self.matrix),
  62. })
  63. self.cache_key = (self.__class__.__name__, self.matrix,
  64. tuple(shader['Coords']), stops)
  65. def spread_gradient(self, gradient, pixel_page_width, pixel_page_height,
  66. matrix):
  67. start = gradient.start()
  68. stop = gradient.finalStop()
  69. stops = list(map(lambda x: [x[0], x[1].getRgbF()], gradient.stops()))
  70. spread = gradient.spread()
  71. if spread != gradient.PadSpread:
  72. inv = matrix.inverted()[0]
  73. page_rect = tuple(map(inv.map, (
  74. QPointF(0, 0), QPointF(pixel_page_width, 0), QPointF(0, pixel_page_height),
  75. QPointF(pixel_page_width, pixel_page_height))))
  76. maxx = maxy = -sys.maxsize-1
  77. minx = miny = sys.maxsize
  78. for p in page_rect:
  79. minx, maxx = min(minx, p.x()), max(maxx, p.x())
  80. miny, maxy = min(miny, p.y()), max(maxy, p.y())
  81. def in_page(point):
  82. return (minx <= point.x() <= maxx and miny <= point.y() <= maxy)
  83. offset = stop - start
  84. llimit, rlimit = start, stop
  85. reflect = False
  86. base_stops = copy.deepcopy(stops)
  87. reversed_stops = list(reversed(stops))
  88. do_reflect = spread == gradient.ReflectSpread
  89. totl = abs(stops[-1][0] - stops[0][0])
  90. intervals = [abs(stops[i+1][0] - stops[i][0])/totl
  91. for i in range(len(stops)-1)]
  92. while in_page(llimit):
  93. reflect ^= True
  94. llimit -= offset
  95. estops = reversed_stops if (reflect and do_reflect) else base_stops
  96. stops = copy.deepcopy(estops) + stops
  97. first_is_reflected = reflect
  98. reflect = False
  99. while in_page(rlimit):
  100. reflect ^= True
  101. rlimit += offset
  102. estops = reversed_stops if (reflect and do_reflect) else base_stops
  103. stops = stops + copy.deepcopy(estops)
  104. start, stop = llimit, rlimit
  105. num = len(stops) // len(base_stops)
  106. if num > 1:
  107. # Adjust the stop parameter values
  108. t = base_stops[0][0]
  109. rlen = totl/num
  110. reflect = first_is_reflected ^ True
  111. intervals = [i*rlen for i in intervals]
  112. rintervals = list(reversed(intervals))
  113. for i in range(num):
  114. reflect ^= True
  115. pos = i * len(base_stops)
  116. tvals = [t]
  117. for ival in (rintervals if reflect and do_reflect else
  118. intervals):
  119. tvals.append(tvals[-1] + ival)
  120. for j in range(len(base_stops)):
  121. stops[pos+j][0] = tvals[j]
  122. t = tvals[-1]
  123. # In case there were rounding errors
  124. stops[-1][0] = base_stops[-1][0]
  125. return start, stop, tuple(Stop(s[0], s[1]) for s in stops)