def test_text(): document = pydyf.PDF() font = pydyf.Dictionary({ 'Type': '/Font', 'Subtype': '/Type1', 'Name': '/F1', 'BaseFont': '/Helvetica', 'Encoding': '/MacRomanEncoding', }) document.add_object(font) draw = pydyf.Stream() draw.begin_text() draw.set_font_size('F1', 200) draw.text_matrix(1, 0, 0, 1, -20, 5) draw.show_text(pydyf.String('l')) draw.show_text(pydyf.String('É')) draw.end_text() document.add_object(draw) document.add_page( pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), 'Resources': pydyf.Dictionary({ 'ProcSet': pydyf.Array(['/PDF', '/Text']), 'Font': pydyf.Dictionary({'F1': font.reference}), }), })) assert_pixels( document, ''' KKKKKKKKKK KKKKKKKKKK KKKKKKKKKK KKKKKKKKKK KKKKKKKKKK __________ __________ __________ __________ __________ ''')
def apply_filters(svg, node, filter_node, font_size): """Apply filters defined in given filter node.""" for child in filter_node: if child.tag == 'feOffset': if filter_node.get('primitiveUnits') == 'objectBoundingBox': bounding_box = svg.calculate_bounding_box(node, font_size) if is_valid_bounding_box(bounding_box): _, _, width, height = bounding_box dx = size(child.get('dx', 0), font_size, 1) * width dy = size(child.get('dy', 0), font_size, 1) * height else: dx = dy = 0 else: dx, dy = svg.point(child.get('dx', 0), child.get('dy', 0), font_size) svg.stream.transform(1, 0, 0, 1, dx, dy) elif child.tag == 'feBlend': mode = child.get('mode', 'normal') mode = mode.replace('-', ' ').title().replace(' ', '') blend_mode = pydyf.Dictionary({ 'Type': '/ExtGState', 'BM': f'/{mode}', }) blend_mode_id = f'bm{len(svg.stream._alpha_states)}' svg.stream._alpha_states[blend_mode_id] = blend_mode svg.stream.set_state(blend_mode_id)
def test_set_color_rgb_stroke(): document = pydyf.PDF() draw = pydyf.Stream() draw.rectangle(2, 2, 5, 6) draw.set_line_width(2) draw.set_color_rgb(0, 0, 255, stroke=True) draw.stroke() document.add_object(draw) document.add_page(pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), })) assert_pixels(document, ''' __________ _BBBBBBB__ _BBBBBBB__ _BB___BB__ _BB___BB__ _BB___BB__ _BB___BB__ _BBBBBBB__ _BBBBBBB__ __________ ''')
def test_fill(): document = pydyf.PDF() draw = pydyf.Stream() draw.rectangle(2, 2, 5, 6) draw.fill() document.add_object(draw) document.add_page(pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), })) assert_pixels(document, ''' __________ __________ __KKKKK___ __KKKKK___ __KKKKK___ __KKKKK___ __KKKKK___ __KKKKK___ __________ __________ ''')
def test_line_to(): document = pydyf.PDF() draw = pydyf.Stream() draw.move_to(2, 2) draw.set_line_width(2) draw.line_to(2, 5) draw.stroke() document.add_object(draw) document.add_page(pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), })) assert_pixels(document, ''' __________ __________ __________ __________ __________ _KK_______ _KK_______ _KK_______ __________ __________ ''')
def test_fill_stroke_and_close(): document = pydyf.PDF() draw = pydyf.Stream() draw.move_to(2, 2) draw.line_to(2, 8) draw.line_to(7, 8) draw.line_to(7, 2) draw.set_color_rgb(255, 0, 0) draw.set_color_rgb(0, 0, 255, stroke=True) draw.set_line_width(2) draw.fill_stroke_and_close() document.add_object(draw) document.add_page(pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), })) assert_pixels(document, ''' __________ _BBBBBBB__ _BBBBBBB__ _BBRRRBB__ _BBRRRBB__ _BBRRRBB__ _BBRRRBB__ _BBBBBBB__ _BBBBBBB__ __________ ''')
def test_clip_even_odd(): document = pydyf.PDF() draw = pydyf.Stream() draw.rectangle(3, 3, 5, 6) draw.rectangle(4, 3, 2, 6) draw.clip(even_odd=True) draw.end() draw.move_to(0, 5) draw.line_to(10, 5) draw.set_color_rgb(255, 0, 0, stroke=True) draw.set_line_width(2) draw.stroke() document.add_object(draw) document.add_page(pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), })) assert_pixels(document, ''' __________ __________ __________ __________ ___R__RR__ ___R__RR__ __________ __________ __________ __________ ''')
def paint_mask(svg, node, mask, font_size): """Apply given mask node.""" mask._etree_node.tag = 'g' if mask.get('maskUnits') == 'userSpaceOnUse': width_ref, height_ref = svg.concrete_width, svg.concrete_height else: width_ref, height_ref = svg.point(node.get('width'), node.get('height'), font_size) mask.attrib['x'] = size(mask.get('x', '-10%'), font_size, width_ref) mask.attrib['y'] = size(mask.get('y', '-10%'), font_size, height_ref) mask.attrib['height'] = size(mask.get('height', '120%'), font_size, height_ref) mask.attrib['width'] = size(mask.get('width', '120%'), font_size, width_ref) if mask.get('maskUnits') == 'userSpaceOnUse': x, y = mask.get('x'), mask.get('y') width, height = mask.get('width'), mask.get('height') mask.attrib['viewBox'] = f'{x} {y} {width} {height}' else: x, y = 0, 0 width, height = width_ref, height_ref alpha_stream = svg.stream.add_group([x, y, width, height]) state = pydyf.Dictionary({ 'Type': '/ExtGState', 'SMask': pydyf.Dictionary({ 'Type': '/Mask', 'S': '/Luminance', 'G': alpha_stream, }), 'ca': 1, 'AIS': 'false', }) svg.stream.set_state(state) svg_stream = svg.stream svg.stream = alpha_stream svg.draw_node(mask, font_size) svg.stream = svg_stream
def test_set_state(): document = pydyf.PDF() graphic_state = pydyf.Dictionary({ 'Type': '/ExtGState', 'LW': 2, }) document.add_object(graphic_state) draw = pydyf.Stream() draw.rectangle(2, 2, 5, 6) draw.set_state('GS') draw.stroke() document.add_object(draw) document.add_page( pydyf.Dictionary({ 'Type': '/Page', 'Parent': document.pages.reference, 'Contents': draw.reference, 'MediaBox': pydyf.Array([0, 0, 10, 10]), 'Resources': pydyf.Dictionary({ 'ExtGState': pydyf.Dictionary({'GS': graphic_state.reference}), }), })) assert_pixels( document, ''' __________ _KKKKKKK__ _KKKKKKK__ _KK___KK__ _KK___KK__ _KK___KK__ _KK___KK__ _KKKKKKK__ _KKKKKKK__ __________ ''')
def draw(self, stream, concrete_width, concrete_height, _image_rendering): # TODO: handle color spaces scale_y, type_, points, positions, colors = self.layout( concrete_width, concrete_height) if type_ == 'solid': stream.rectangle(0, 0, concrete_width, concrete_height) red, green, blue, alpha = colors[0] stream.set_color_rgb(red, green, blue) if alpha != 1: stream.set_alpha(alpha, stroke=False) stream.fill() return alphas = [color[3] for color in colors] alpha_couples = [(alphas[i], alphas[i + 1]) for i in range(len(alphas) - 1)] color_couples = [[colors[i][:3], colors[i + 1][:3], 1] for i in range(len(colors) - 1)] # Premultiply colors for i, alpha in enumerate(alphas): if alpha == 0: if i > 0: color_couples[i - 1][1] = color_couples[i - 1][0] if i < len(colors) - 1: color_couples[i][0] = color_couples[i][1] for i, (a0, a1) in enumerate(alpha_couples): if 0 not in (a0, a1) and (a0, a1) != (1, 1): color_couples[i][2] = a0 / a1 shading = stream.add_shading() shading['ShadingType'] = 2 if type_ == 'linear' else 3 shading['ColorSpace'] = '/DeviceRGB' shading['Domain'] = pydyf.Array([positions[0], positions[-1]]) shading['Coords'] = pydyf.Array(points) shading['Function'] = pydyf.Dictionary({ 'FunctionType': 3, 'Domain': pydyf.Array([positions[0], positions[-1]]), 'Encode': pydyf.Array((len(colors) - 1) * [0, 1]), 'Bounds': pydyf.Array(positions[1:-1]), 'Functions': pydyf.Array([ pydyf.Dictionary({ 'FunctionType': 2, 'Domain': pydyf.Array([0, 1]), 'C0': pydyf.Array(c0), 'C1': pydyf.Array(c1), 'N': n, }) for c0, c1, n in color_couples ]), }) if not self.repeating: shading['Extend'] = pydyf.Array([b'true', b'true']) stream.transform(d=scale_y) if any(alpha != 1 for alpha in alphas): alpha_stream = stream.add_group( [0, 0, concrete_width, concrete_height]) alpha_state = pydyf.Dictionary({ 'Type': '/ExtGState', 'SMask': pydyf.Dictionary({ 'Type': '/Mask', 'S': '/Luminosity', 'G': alpha_stream, }), 'ca': 1, 'AIS': 'false', }) stream.set_state(alpha_state) alpha_shading = alpha_stream.add_shading() alpha_shading['ShadingType'] = 2 if type_ == 'linear' else 3 alpha_shading['ColorSpace'] = '/DeviceGray' alpha_shading['Domain'] = pydyf.Array( [positions[0], positions[-1]]) alpha_shading['Coords'] = pydyf.Array(points) alpha_shading['Function'] = pydyf.Dictionary({ 'FunctionType': 3, 'Domain': pydyf.Array([positions[0], positions[-1]]), 'Encode': pydyf.Array((len(colors) - 1) * [0, 1]), 'Bounds': pydyf.Array(positions[1:-1]), 'Functions': pydyf.Array([ pydyf.Dictionary({ 'FunctionType': 2, 'Domain': pydyf.Array([0, 1]), 'C0': pydyf.Array([c0]), 'C1': pydyf.Array([c1]), 'N': 1, }) for c0, c1 in alpha_couples ]), }) if not self.repeating: alpha_shading['Extend'] = pydyf.Array([b'true', b'true']) alpha_stream.transform(d=scale_y) alpha_stream.stream = [f'/{alpha_shading.id} sh'] stream.shading(shading.id)
def draw_gradient(svg, node, gradient, font_size, opacity, stroke): """Draw given gradient node.""" # TODO: merge with Gradient.draw from ..document import Matrix positions = [] colors = [] for child in gradient: positions.append( max(positions[-1] if positions else 0, size(child.get('offset'), font_size, 1))) stop_opacity = float(child.get('stop-opacity', 1)) * opacity stop_color = color(child.get('stop-color', 'black')) if stop_opacity < 1: stop_color = tuple(stop_color[:3] + (stop_color[3] * stop_opacity, )) colors.append(stop_color) if not colors: return False elif len(colors) == 1: red, green, blue, alpha = colors[0] svg.stream.set_color_rgb(red, green, blue) if alpha != 1: svg.stream.set_alpha(alpha, stroke=stroke) return True bounding_box = svg.calculate_bounding_box(node, font_size, stroke) if not is_valid_bounding_box(bounding_box): return False x, y = bounding_box[0], bounding_box[1] node_x, node_y = svg.point(node.get('x'), node.get('y'), font_size) matrix = Matrix() if gradient.get('gradientUnits') == 'userSpaceOnUse': viewbox = svg.get_viewbox() if viewbox: width, height = viewbox[2], viewbox[3] else: width, height = svg.concrete_width, svg.concrete_height else: width, height = bounding_box[2], bounding_box[3] spread = gradient.get('spreadMethod', 'pad') if spread not in ('repeat', 'reflect'): # Add explicit colors at boundaries if needed, because PDF doesn’t # extend color stops that are not displayed if positions[0] == positions[1]: if gradient.tag == 'radialGradient': # Avoid negative radius for radial gradients positions.insert(0, 0) else: positions.insert(0, positions[0] - 1) colors.insert(0, colors[0]) if positions[-2] == positions[-1]: positions.append(positions[-1] + 1) colors.append(colors[-1]) if gradient.tag == 'linearGradient': shading_type = 2 x1, y1 = (size(gradient.get('x1', 0), font_size, 1), size(gradient.get('y1', 0), font_size, 1)) x2, y2 = (size(gradient.get('x2', '100%'), font_size, 1), size(gradient.get('y2', 0), font_size, 1)) if gradient.get('gradientUnits') == 'userSpaceOnUse': x1 -= x y1 -= y x2 -= x y2 -= y else: length = min(width, height) x1 *= length y1 *= length x2 *= length y2 *= length a = (width / height) if height < width else 1 d = (height / width) if height > width else 1 matrix = Matrix(a=a, d=d) @ matrix positions, colors, coords = spread_linear_gradient( spread, positions, colors, x1, y1, x2, y2) else: assert gradient.tag == 'radialGradient' shading_type = 3 cx, cy = (size(gradient.get('cx', '50%'), font_size, 1), size(gradient.get('cy', '50%'), font_size, 1)) r = size(gradient.get('r', '50%'), font_size, 1) fx, fy = (size(gradient.get('fx', cx), font_size, width), size(gradient.get('fy', cy), font_size, height)) fr = size(gradient.get('fr', 0), font_size, 1) if gradient.get('gradientUnits') == 'userSpaceOnUse': cx -= x cy -= y fx -= x fy -= y else: length = min(width, height) cx *= length cy *= length r *= length fx *= length fy *= length fr *= length a = (width / height) if height < width else 1 d = (height / width) if height > width else 1 matrix = Matrix(a=a, d=d) @ matrix positions, colors, coords = spread_radial_gradient( spread, positions, colors, fx, fy, fr, cx, cy, r, width, height) alphas = [color[3] for color in colors] alpha_couples = [(alphas[i], alphas[i + 1]) for i in range(len(alphas) - 1)] color_couples = [[colors[i][:3], colors[i + 1][:3], 1] for i in range(len(colors) - 1)] # Premultiply colors for i, alpha in enumerate(alphas): if alpha == 0: if i > 0: color_couples[i - 1][1] = color_couples[i - 1][0] if i < len(colors) - 1: color_couples[i][0] = color_couples[i][1] for i, (a0, a1) in enumerate(alpha_couples): if 0 not in (a0, a1) and (a0, a1) != (1, 1): color_couples[i][2] = a0 / a1 if 'gradientTransform' in gradient.attrib: transform_matrix = transform(gradient.get('gradientTransform'), font_size, svg.normalized_diagonal) matrix = transform_matrix @ matrix a, b, c, d, e, f = matrix.values width /= a height /= d pattern_x = e / a pattern_y = f / d matrix = Matrix(e=x - node_x, f=y - node_y) @ matrix @ svg.stream.ctm pattern = svg.stream.add_pattern(-pattern_x, -pattern_y, width, height, width, height, matrix) group = pattern.add_group([0, 0, width, height]) shading = group.add_shading() shading['ShadingType'] = shading_type shading['ColorSpace'] = '/DeviceRGB' shading['Domain'] = pydyf.Array([positions[0], positions[-1]]) shading['Coords'] = pydyf.Array(coords) shading['Function'] = pydyf.Dictionary({ 'FunctionType': 3, 'Domain': pydyf.Array([positions[0], positions[-1]]), 'Encode': pydyf.Array((len(colors) - 1) * [0, 1]), 'Bounds': pydyf.Array(positions[1:-1]), 'Functions': pydyf.Array([ pydyf.Dictionary({ 'FunctionType': 2, 'Domain': pydyf.Array([positions[0], positions[-1]]), 'C0': pydyf.Array(c0), 'C1': pydyf.Array(c1), 'N': n, }) for c0, c1, n in color_couples ]), }) if spread not in ('repeat', 'reflect'): shading['Extend'] = pydyf.Array([b'true', b'true']) if any(alpha != 1 for alpha in alphas): alpha_stream = group.add_group( [0, 0, svg.concrete_width, svg.concrete_height]) state = pydyf.Dictionary({ 'Type': '/ExtGState', 'SMask': pydyf.Dictionary({ 'Type': '/Mask', 'S': '/Luminosity', 'G': alpha_stream, }), 'ca': 1, 'AIS': 'false', }) group.set_state(state) alpha_shading = alpha_stream.add_shading() alpha_shading['ShadingType'] = shading_type alpha_shading['ColorSpace'] = '/DeviceGray' alpha_shading['Domain'] = pydyf.Array([positions[0], positions[-1]]) alpha_shading['Coords'] = pydyf.Array(coords) alpha_shading['Function'] = pydyf.Dictionary({ 'FunctionType': 3, 'Domain': pydyf.Array([positions[0], positions[-1]]), 'Encode': pydyf.Array((len(colors) - 1) * [0, 1]), 'Bounds': pydyf.Array(positions[1:-1]), 'Functions': pydyf.Array([ pydyf.Dictionary({ 'FunctionType': 2, 'Domain': pydyf.Array([0, 1]), 'C0': pydyf.Array([c0]), 'C1': pydyf.Array([c1]), 'N': 1, }) for c0, c1 in alpha_couples ]), }) if spread not in ('repeat', 'reflect'): alpha_shading['Extend'] = pydyf.Array([b'true', b'true']) alpha_stream.stream = [f'/{alpha_shading.id} sh'] group.shading(shading.id) pattern.draw_x_object(group.id) svg.stream.color_space('Pattern', stroke=stroke) svg.stream.set_color_special(pattern.id, stroke=stroke) return True