| /* |
| * Copyright © 2026 Behdad Esfahbod |
| * |
| * This is part of HarfBuzz, a text shaping library. |
| * |
| * Permission is hereby granted, without written agreement and without |
| * license or royalty fees, to use, copy, modify, and distribute this |
| * software and its documentation for any purpose, provided that the |
| * above copyright notice and the following two paragraphs appear in |
| * all copies of this software. |
| * |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| * |
| * Author(s): Behdad Esfahbod |
| */ |
| |
| #include "hb.hh" |
| |
| #include "hb-vector-paint.hh" |
| #include "hb-vector-draw.hh" |
| #include "hb-paint.hh" |
| |
| #include <math.h> |
| #include <stdio.h> |
| |
| #ifdef HAVE_ZLIB |
| #include <zlib.h> |
| #endif |
| |
| /* PDF paint backend for COLRv0/v1 color font rendering. |
| * |
| * Supports: |
| * - Solid colors with alpha (via ExtGState) |
| * - Glyph and rectangle clipping |
| * - Affine transforms |
| * - Groups (save/restore) |
| * - Linear gradients (PDF Type 2 axial shading) |
| * - Radial gradients (PDF Type 3 radial shading) |
| * - Sweep gradients (approximated via solid fallback) |
| */ |
| |
| |
| /* ---- PDF object collector ---- */ |
| |
| struct hb_pdf_obj_t |
| { |
| hb_vector_t<char> data; |
| }; |
| |
| /* Collects extra PDF objects (shadings, functions, ExtGState) |
| * during painting. Referenced from the content stream by name |
| * (e.g. /SH0, /GS0) and emitted at render time. */ |
| static bool |
| hb_pdf_build_indexed_smask (hb_vector_t<char> *out, |
| const char *idat_data, unsigned idat_len, |
| unsigned width, unsigned height, |
| const uint8_t *trns, unsigned trns_len); |
| |
| struct hb_pdf_resources_t |
| { |
| hb_vector_t<hb_pdf_obj_t> objects; /* extra objects, starting at id 5 */ |
| hb_vector_t<char> extgstate_dict; /* /GS0 5 0 R /GS1 6 0 R ... */ |
| hb_vector_t<char> shading_dict; /* /SH0 7 0 R ... */ |
| hb_vector_t<char> xobject_dict; /* /Im0 8 0 R ... */ |
| unsigned extgstate_count = 0; |
| unsigned shading_count = 0; |
| unsigned xobject_count = 0; |
| |
| unsigned add_object (hb_vector_t<char> &&obj_data) |
| { |
| unsigned id = 5 + objects.length; /* objects 1-4 are fixed */ |
| hb_pdf_obj_t obj; |
| obj.data = std::move (obj_data); |
| objects.push (std::move (obj)); |
| return id; |
| } |
| |
| /* Add ExtGState for fill opacity, return resource name index. */ |
| unsigned add_extgstate_alpha (float alpha, unsigned precision) |
| { |
| unsigned idx = extgstate_count++; |
| hb_vector_t<char> obj; |
| hb_buf_append_str (&obj, "<< /Type /ExtGState /ca "); |
| hb_buf_append_num (&obj, alpha, 4); |
| hb_buf_append_str (&obj, " >>"); |
| unsigned obj_id = add_object (std::move (obj)); |
| |
| /* Add to resource dict. */ |
| hb_buf_append_str (&extgstate_dict, "/GS"); |
| hb_buf_append_unsigned (&extgstate_dict, idx); |
| hb_buf_append_c (&extgstate_dict, ' '); |
| hb_buf_append_unsigned (&extgstate_dict, obj_id); |
| hb_buf_append_str (&extgstate_dict, " 0 R "); |
| (void) precision; |
| return idx; |
| } |
| |
| /* Add ExtGState for blend mode, return resource name index. */ |
| unsigned add_extgstate_blend (const char *bm) |
| { |
| unsigned idx = extgstate_count++; |
| hb_vector_t<char> obj; |
| hb_buf_append_str (&obj, "<< /Type /ExtGState /BM /"); |
| hb_buf_append_str (&obj, bm); |
| hb_buf_append_str (&obj, " >>"); |
| unsigned obj_id = add_object (std::move (obj)); |
| |
| hb_buf_append_str (&extgstate_dict, "/GS"); |
| hb_buf_append_unsigned (&extgstate_dict, idx); |
| hb_buf_append_c (&extgstate_dict, ' '); |
| hb_buf_append_unsigned (&extgstate_dict, obj_id); |
| hb_buf_append_str (&extgstate_dict, " 0 R "); |
| return idx; |
| } |
| |
| /* Add a shading + function for a gradient, return resource name index. */ |
| unsigned add_shading (hb_vector_t<char> &&shading_data) |
| { |
| unsigned idx = shading_count++; |
| unsigned obj_id = add_object (std::move (shading_data)); |
| |
| hb_buf_append_str (&shading_dict, "/SH"); |
| hb_buf_append_unsigned (&shading_dict, idx); |
| hb_buf_append_c (&shading_dict, ' '); |
| hb_buf_append_unsigned (&shading_dict, obj_id); |
| hb_buf_append_str (&shading_dict, " 0 R "); |
| return idx; |
| } |
| |
| /* Add an XObject image, return resource name index. |
| * idat_data/idat_len is the concatenated PNG IDAT payload (zlib data). |
| * colors is 1 (gray), 3 (RGB), or 4 (RGBA); for indexed, pass colors=1. |
| * plte/plte_len is the PLTE chunk data for indexed images (may be null). */ |
| unsigned add_xobject_png_image (const char *idat_data, unsigned idat_len, |
| unsigned width, unsigned height, |
| unsigned colors, bool has_alpha, |
| const uint8_t *plte = nullptr, |
| unsigned plte_len = 0, |
| const uint8_t *trns = nullptr, |
| unsigned trns_len = 0) |
| { |
| unsigned idx = xobject_count++; |
| hb_vector_t<char> obj; |
| hb_buf_append_str (&obj, "<< /Type /XObject /Subtype /Image\n"); |
| hb_buf_append_str (&obj, "/Width "); |
| hb_buf_append_unsigned (&obj, width); |
| hb_buf_append_str (&obj, " /Height "); |
| hb_buf_append_unsigned (&obj, height); |
| hb_buf_append_str (&obj, "\n/BitsPerComponent 8\n"); |
| |
| if (plte && plte_len >= 3) |
| { |
| /* Indexed color: /ColorSpace [/Indexed /DeviceRGB N <hex palette>] */ |
| unsigned n_entries = plte_len / 3; |
| hb_buf_append_str (&obj, "/ColorSpace [/Indexed /DeviceRGB "); |
| hb_buf_append_unsigned (&obj, n_entries - 1); |
| hb_buf_append_str (&obj, " <"); |
| for (unsigned i = 0; i < n_entries * 3; i++) |
| { |
| hb_buf_append_c (&obj, "0123456789ABCDEF"[plte[i] >> 4]); |
| hb_buf_append_c (&obj, "0123456789ABCDEF"[plte[i] & 0xF]); |
| } |
| hb_buf_append_str (&obj, ">]\n"); |
| } |
| else |
| { |
| hb_buf_append_str (&obj, "/ColorSpace "); |
| unsigned color_channels = has_alpha ? colors - 1 : colors; |
| hb_buf_append_str (&obj, color_channels == 1 ? "/DeviceGray" : "/DeviceRGB"); |
| hb_buf_append_c (&obj, '\n'); |
| } |
| |
| /* Build SMask for indexed images with tRNS transparency. */ |
| unsigned smask_id = 0; |
| if (plte && trns && trns_len) |
| { |
| hb_vector_t<char> smask_stream; |
| if (hb_pdf_build_indexed_smask (&smask_stream, idat_data, idat_len, |
| width, height, trns, trns_len)) |
| { |
| hb_vector_t<char> smask_obj; |
| hb_buf_append_str (&smask_obj, "<< /Type /XObject /Subtype /Image\n"); |
| hb_buf_append_str (&smask_obj, "/Width "); |
| hb_buf_append_unsigned (&smask_obj, width); |
| hb_buf_append_str (&smask_obj, " /Height "); |
| hb_buf_append_unsigned (&smask_obj, height); |
| hb_buf_append_str (&smask_obj, "\n/ColorSpace /DeviceGray /BitsPerComponent 8\n"); |
| hb_buf_append_str (&smask_obj, "/Length "); |
| hb_buf_append_unsigned (&smask_obj, smask_stream.length); |
| hb_buf_append_str (&smask_obj, " >>\nstream\n"); |
| hb_buf_append_len (&smask_obj, smask_stream.arrayZ, smask_stream.length); |
| hb_buf_append_str (&smask_obj, "\nendstream"); |
| smask_id = add_object (std::move (smask_obj)); |
| } |
| } |
| |
| if (smask_id) |
| { |
| hb_buf_append_str (&obj, "/SMask "); |
| hb_buf_append_unsigned (&obj, smask_id); |
| hb_buf_append_str (&obj, " 0 R\n"); |
| } |
| |
| hb_buf_append_str (&obj, "/Filter /FlateDecode\n"); |
| hb_buf_append_str (&obj, "/DecodeParms << /Predictor 15 /Colors "); |
| hb_buf_append_unsigned (&obj, colors); |
| hb_buf_append_str (&obj, " /BitsPerComponent 8 /Columns "); |
| hb_buf_append_unsigned (&obj, width); |
| hb_buf_append_str (&obj, " >>\n"); |
| hb_buf_append_str (&obj, "/Length "); |
| hb_buf_append_unsigned (&obj, idat_len); |
| hb_buf_append_str (&obj, " >>\nstream\n"); |
| hb_buf_append_len (&obj, idat_data, idat_len); |
| hb_buf_append_str (&obj, "\nendstream"); |
| |
| (void) has_alpha; |
| |
| unsigned obj_id = add_object (std::move (obj)); |
| |
| hb_buf_append_str (&xobject_dict, "/Im"); |
| hb_buf_append_unsigned (&xobject_dict, idx); |
| hb_buf_append_c (&xobject_dict, ' '); |
| hb_buf_append_unsigned (&xobject_dict, obj_id); |
| hb_buf_append_str (&xobject_dict, " 0 R "); |
| return idx; |
| } |
| }; |
| |
| /* Store resources pointer in the paint struct's defs buffer |
| * (repurposed — defs is unused for PDF). */ |
| static hb_pdf_resources_t * |
| hb_pdf_get_resources (hb_vector_paint_t *paint) |
| { |
| if (!paint->defs.length) |
| { |
| /* First call: allocate and store. */ |
| auto *res = (hb_pdf_resources_t *) hb_calloc (1, sizeof (hb_pdf_resources_t)); |
| if (unlikely (!res)) return nullptr; |
| new (res) hb_pdf_resources_t (); |
| if (unlikely (!paint->defs.resize (sizeof (void *)))) |
| { |
| res->~hb_pdf_resources_t (); |
| hb_free (res); |
| return nullptr; |
| } |
| memcpy (paint->defs.arrayZ, &res, sizeof (void *)); |
| } |
| hb_pdf_resources_t *res; |
| memcpy (&res, paint->defs.arrayZ, sizeof (void *)); |
| return res; |
| } |
| |
| void |
| hb_vector_paint_pdf_free_resources (hb_vector_paint_t *paint) |
| { |
| if (paint->defs.length >= sizeof (void *)) |
| { |
| hb_pdf_resources_t *res; |
| memcpy (&res, paint->defs.arrayZ, sizeof (void *)); |
| if (res) |
| { |
| res->~hb_pdf_resources_t (); |
| hb_free (res); |
| } |
| } |
| paint->defs.clear (); |
| } |
| |
| |
| /* ---- helpers ---- */ |
| |
| static hb_bool_t |
| hb_pdf_paint_ensure_initialized (hb_vector_paint_t *paint) |
| { |
| if (paint->group_stack.length) |
| return !paint->group_stack.in_error () && |
| !paint->group_stack.tail ().in_error (); |
| if (unlikely (!paint->group_stack.push_or_fail ())) |
| return false; |
| paint->group_stack.tail ().alloc (4096); |
| if (unlikely (paint->group_stack.tail ().in_error ())) |
| { |
| paint->group_stack.pop (); |
| return false; |
| } |
| return !paint->group_stack.in_error (); |
| } |
| |
| /* Emit a glyph outline as PDF path operators into buf. */ |
| static void |
| hb_pdf_emit_glyph_path (hb_vector_paint_t *paint, |
| hb_font_t *font, |
| hb_codepoint_t glyph, |
| hb_vector_t<char> *buf) |
| { |
| hb_vector_draw_t tmp; |
| tmp.format = HB_VECTOR_FORMAT_PDF; |
| tmp.precision = paint->precision; |
| tmp.transform = {1, 0, 0, 1, 0, 0}; |
| tmp.path.alloc (1024); |
| |
| hb_font_draw_glyph (font, glyph, |
| hb_vector_draw_get_funcs (), |
| &tmp); |
| |
| hb_buf_append_len (buf, tmp.path.arrayZ, tmp.path.length); |
| } |
| |
| |
| /* ---- paint callbacks ---- */ |
| |
| static void |
| hb_pdf_paint_push_transform (hb_paint_funcs_t *, |
| void *paint_data, |
| float xx, float yx, |
| float xy, float yy, |
| float dx, float dy, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "q\n"); |
| hb_buf_append_num (&body, xx, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, yx, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, xy, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, yy, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, dx, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, dy, paint->precision); |
| hb_buf_append_str (&body, " cm\n"); |
| } |
| |
| static void |
| hb_pdf_paint_pop_transform (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| hb_buf_append_str (&paint->current_body (), "Q\n"); |
| } |
| |
| static void |
| hb_pdf_paint_push_clip_glyph (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_codepoint_t glyph, |
| hb_font_t *font, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "q\n"); |
| hb_pdf_emit_glyph_path (paint, font, glyph, &body); |
| hb_buf_append_str (&body, "W n\n"); |
| } |
| |
| static void |
| hb_pdf_paint_push_clip_rectangle (hb_paint_funcs_t *, |
| void *paint_data, |
| float xmin, float ymin, |
| float xmax, float ymax, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "q\n"); |
| hb_buf_append_num (&body, xmin, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, ymin, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, xmax - xmin, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, ymax - ymin, paint->precision); |
| hb_buf_append_str (&body, " re W n\n"); |
| } |
| |
| static void |
| hb_pdf_paint_pop_clip (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| hb_buf_append_str (&paint->current_body (), "Q\n"); |
| } |
| |
| /* Paint a solid color, including alpha via ExtGState. */ |
| static void |
| hb_pdf_paint_solid_color (hb_vector_paint_t *paint, hb_color_t c) |
| { |
| auto &body = paint->current_body (); |
| |
| float r = hb_color_get_red (c) / 255.f; |
| float g = hb_color_get_green (c) / 255.f; |
| float b = hb_color_get_blue (c) / 255.f; |
| float a = hb_color_get_alpha (c) / 255.f; |
| |
| if (a < 1.f / 255.f) |
| return; |
| |
| /* Set alpha via ExtGState if needed. */ |
| if (a < 1.f - 1.f / 512.f) |
| { |
| auto *res = hb_pdf_get_resources (paint); |
| if (res) |
| { |
| unsigned gs_idx = res->add_extgstate_alpha (a, paint->precision); |
| hb_buf_append_str (&body, "/GS"); |
| hb_buf_append_unsigned (&body, gs_idx); |
| hb_buf_append_str (&body, " gs\n"); |
| } |
| } |
| |
| /* Set fill color. */ |
| hb_buf_append_num (&body, r, 4); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, g, 4); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, b, 4); |
| hb_buf_append_str (&body, " rg\n"); |
| |
| /* Paint a huge rect (will be clipped). */ |
| hb_buf_append_str (&body, "-32767 -32767 65534 65534 re f\n"); |
| } |
| |
| static void |
| hb_pdf_paint_color (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_bool_t is_foreground, |
| hb_color_t color, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| |
| hb_color_t c = color; |
| if (is_foreground) |
| c = HB_COLOR (hb_color_get_blue (paint->foreground), |
| hb_color_get_green (paint->foreground), |
| hb_color_get_red (paint->foreground), |
| (unsigned) hb_color_get_alpha (paint->foreground) * |
| hb_color_get_alpha (color) / 255); |
| |
| hb_pdf_paint_solid_color (paint, c); |
| } |
| |
| /* Build an uncompressed alpha mask from indexed PNG IDAT data + tRNS. |
| * Decompresses IDAT, un-filters PNG rows, maps palette indices to alpha. */ |
| static bool |
| hb_pdf_build_indexed_smask (hb_vector_t<char> *out, |
| const char *idat_data, unsigned idat_len, |
| unsigned width, unsigned height, |
| const uint8_t *trns, unsigned trns_len) |
| { |
| #ifndef HAVE_ZLIB |
| (void) out; (void) idat_data; (void) idat_len; |
| (void) width; (void) height; (void) trns; (void) trns_len; |
| return false; |
| #else |
| /* Decompress IDAT (zlib). */ |
| unsigned raw_len = (width + 1) * height; /* 1 filter byte per row + width bytes */ |
| uint8_t *raw = (uint8_t *) hb_malloc (raw_len); |
| if (!raw) return false; |
| HB_SCOPE_GUARD (hb_free (raw)); |
| |
| z_stream stream = {}; |
| stream.next_in = (Bytef *) idat_data; |
| stream.avail_in = idat_len; |
| stream.next_out = (Bytef *) raw; |
| stream.avail_out = raw_len; |
| |
| if (inflateInit (&stream) != Z_OK) |
| return false; |
| int status = inflate (&stream, Z_FINISH); |
| inflateEnd (&stream); |
| if (status != Z_STREAM_END) |
| return false; |
| |
| /* Un-filter and map to alpha. */ |
| if (!out->resize (width * height)) |
| return false; |
| |
| uint8_t *unfiltered = (uint8_t *) hb_malloc (width); |
| if (!unfiltered) |
| return false; |
| HB_SCOPE_GUARD (hb_free (unfiltered)); |
| |
| uint8_t *prev_unfiltered = (uint8_t *) hb_calloc (width, 1); |
| if (!prev_unfiltered) |
| return false; |
| HB_SCOPE_GUARD (hb_free (prev_unfiltered)); |
| |
| for (unsigned y = 0; y < height; y++) |
| { |
| uint8_t *row = raw + y * (width + 1); |
| uint8_t filter = row[0]; |
| uint8_t *pixels = row + 1; |
| |
| for (unsigned x = 0; x < width; x++) |
| { |
| uint8_t a = (x > 0) ? unfiltered[x - 1] : 0; |
| uint8_t b = prev_unfiltered[x]; |
| uint8_t c = (x > 0) ? prev_unfiltered[x - 1] : 0; |
| uint8_t val = pixels[x]; |
| |
| switch (filter) |
| { |
| case 0: unfiltered[x] = val; break; /* None */ |
| case 1: unfiltered[x] = val + a; break; /* Sub */ |
| case 2: unfiltered[x] = val + b; break; /* Up */ |
| case 3: unfiltered[x] = val + (uint8_t) (((unsigned) a + b) / 2); break; /* Average */ |
| case 4: /* Paeth */ |
| { |
| int p = (int) a + (int) b - (int) c; |
| int pa = abs (p - (int) a); |
| int pb = abs (p - (int) b); |
| int pc = abs (p - (int) c); |
| uint8_t pr = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; |
| unfiltered[x] = val + pr; |
| break; |
| } |
| default: unfiltered[x] = val; break; |
| } |
| |
| /* Map palette index to alpha. */ |
| uint8_t idx = unfiltered[x]; |
| out->arrayZ[y * width + x] = (idx < trns_len) ? trns[idx] : 0xFF; |
| } |
| |
| /* Swap buffers. */ |
| uint8_t *tmp = prev_unfiltered; |
| prev_unfiltered = unfiltered; |
| unfiltered = tmp; |
| } |
| |
| return true; |
| #endif |
| } |
| |
| /* Read a big-endian uint32 from a byte pointer. */ |
| static inline uint32_t |
| hb_pdf_png_u32 (const uint8_t *p) |
| { |
| return ((uint32_t) p[0] << 24) | ((uint32_t) p[1] << 16) | |
| ((uint32_t) p[2] << 8) | (uint32_t) p[3]; |
| } |
| |
| static hb_bool_t |
| hb_pdf_paint_image (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_blob_t *image, |
| unsigned width, |
| unsigned height, |
| hb_tag_t format, |
| float slant HB_UNUSED, |
| hb_glyph_extents_t *extents, |
| void *) |
| { |
| if (format != HB_TAG ('p','n','g',' ')) |
| return false; |
| if (!extents || !width || !height) |
| return false; |
| |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return false; |
| auto *res = hb_pdf_get_resources (paint); |
| if (unlikely (!res)) |
| return false; |
| |
| unsigned len = 0; |
| const uint8_t *data = (const uint8_t *) hb_blob_get_data (image, &len); |
| if (!data || len < 8) |
| return false; |
| |
| /* Verify PNG signature. */ |
| static const uint8_t png_sig[8] = {137, 80, 78, 71, 13, 10, 26, 10}; |
| if (hb_memcmp (data, png_sig, 8) != 0) |
| return false; |
| |
| /* Parse PNG chunks: extract IHDR and concatenate IDAT payloads. */ |
| unsigned png_w = 0, png_h = 0; |
| unsigned colors = 3; /* default RGB */ |
| uint8_t color_type = 2; |
| bool has_alpha = false; |
| const uint8_t *plte_data = nullptr; |
| unsigned plte_len = 0; |
| const uint8_t *trns_data = nullptr; |
| unsigned trns_len = 0; |
| hb_vector_t<char> idat; |
| |
| unsigned pos = 8; |
| while (pos + 12 <= len) |
| { |
| uint32_t chunk_len = hb_pdf_png_u32 (data + pos); |
| uint32_t chunk_type = hb_pdf_png_u32 (data + pos + 4); |
| if (pos + 12 + chunk_len > len) |
| break; |
| const uint8_t *chunk_data = data + pos + 8; |
| |
| if (chunk_type == 0x49484452u) /* IHDR */ |
| { |
| if (chunk_len < 13) return false; |
| png_w = hb_pdf_png_u32 (chunk_data); |
| png_h = hb_pdf_png_u32 (chunk_data + 4); |
| uint8_t bit_depth = chunk_data[8]; |
| color_type = chunk_data[9]; |
| if (bit_depth != 8) return false; /* only 8-bit supported */ |
| |
| switch (color_type) |
| { |
| case 0: colors = 1; has_alpha = false; break; /* Grayscale */ |
| case 2: colors = 3; has_alpha = false; break; /* RGB */ |
| case 3: colors = 1; has_alpha = false; break; /* Indexed */ |
| case 4: colors = 2; has_alpha = true; break; /* Gray+Alpha */ |
| case 6: colors = 4; has_alpha = true; break; /* RGBA */ |
| default: return false; |
| } |
| } |
| else if (chunk_type == 0x504C5445u) /* PLTE */ |
| { |
| plte_data = chunk_data; |
| plte_len = chunk_len; |
| } |
| else if (chunk_type == 0x74524E53u) /* tRNS */ |
| { |
| trns_data = chunk_data; |
| trns_len = chunk_len; |
| } |
| else if (chunk_type == 0x49444154u) /* IDAT */ |
| { |
| hb_buf_append_len (&idat, (const char *) chunk_data, chunk_len); |
| } |
| else if (chunk_type == 0x49454E44u) /* IEND */ |
| break; |
| |
| pos += 12 + chunk_len; |
| } |
| |
| if (!png_w || !png_h || !idat.length) |
| return false; |
| |
| unsigned im_idx = res->add_xobject_png_image (idat.arrayZ, idat.length, |
| png_w, png_h, |
| colors, has_alpha, |
| plte_data, plte_len, |
| trns_data, trns_len); |
| |
| /* Emit: save state, set CTM to map image (0,0)-(1,1) to extents, paint. */ |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "q\n"); |
| |
| /* Image space: (0,0) at bottom-left, (1,1) at top-right. |
| * We need to map to extents (x_bearing, y_bearing+height) .. (x_bearing+width, y_bearing). |
| * Since font coords are Y-up like PDF, y_bearing is the top. */ |
| float ix = (float) extents->x_bearing; |
| float iy = (float) extents->y_bearing + (float) extents->height; |
| float iw = (float) extents->width; |
| float ih = (float) -extents->height; /* negative because image Y goes up but height is negative in extents */ |
| |
| hb_buf_append_num (&body, iw, paint->precision); |
| hb_buf_append_str (&body, " 0 0 "); |
| hb_buf_append_num (&body, ih, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, ix, paint->precision); |
| hb_buf_append_c (&body, ' '); |
| hb_buf_append_num (&body, iy, paint->precision); |
| hb_buf_append_str (&body, " cm\n"); |
| |
| hb_buf_append_str (&body, "/Im"); |
| hb_buf_append_unsigned (&body, im_idx); |
| hb_buf_append_str (&body, " Do\nQ\n"); |
| |
| return true; |
| } |
| |
| /* ---- Gradient helpers ---- */ |
| |
| /* Build a PDF Type 2 (exponential interpolation) function for |
| * a single color stop pair. */ |
| static void |
| hb_pdf_build_interpolation_function (hb_vector_t<char> *obj, |
| hb_color_t c0, hb_color_t c1) |
| { |
| hb_buf_append_str (obj, "<< /FunctionType 2 /Domain [0 1] /N 1\n"); |
| hb_buf_append_str (obj, "/C0 ["); |
| hb_buf_append_num (obj, hb_color_get_red (c0) / 255.f, 4); |
| hb_buf_append_c (obj, ' '); |
| hb_buf_append_num (obj, hb_color_get_green (c0) / 255.f, 4); |
| hb_buf_append_c (obj, ' '); |
| hb_buf_append_num (obj, hb_color_get_blue (c0) / 255.f, 4); |
| hb_buf_append_str (obj, "]\n/C1 ["); |
| hb_buf_append_num (obj, hb_color_get_red (c1) / 255.f, 4); |
| hb_buf_append_c (obj, ' '); |
| hb_buf_append_num (obj, hb_color_get_green (c1) / 255.f, 4); |
| hb_buf_append_c (obj, ' '); |
| hb_buf_append_num (obj, hb_color_get_blue (c1) / 255.f, 4); |
| hb_buf_append_str (obj, "] >>"); |
| } |
| |
| /* Build a stitching function (Type 3) for multiple color stops. */ |
| static unsigned |
| hb_pdf_build_gradient_function (hb_pdf_resources_t *res, |
| hb_vector_paint_t *paint, |
| hb_color_line_t *color_line) |
| { |
| unsigned count = hb_color_line_get_color_stops (color_line, 0, nullptr, nullptr); |
| paint->color_stops_scratch.resize (count); |
| hb_color_line_get_color_stops (color_line, 0, &count, paint->color_stops_scratch.arrayZ); |
| |
| if (count < 2) |
| { |
| /* Single stop: constant function. */ |
| hb_color_t c = count ? paint->color_stops_scratch.arrayZ[0].color |
| : HB_COLOR (0, 0, 0, 255); |
| hb_vector_t<char> obj; |
| hb_pdf_build_interpolation_function (&obj, c, c); |
| return res->add_object (std::move (obj)); |
| } |
| |
| /* Sort by offset. */ |
| paint->color_stops_scratch.as_array ().qsort ( |
| [] (const hb_color_stop_t &a, const hb_color_stop_t &b) |
| { return a.offset < b.offset; }); |
| |
| if (count == 2) |
| { |
| /* Two stops: single interpolation function. */ |
| hb_vector_t<char> obj; |
| hb_pdf_build_interpolation_function (&obj, |
| paint->color_stops_scratch.arrayZ[0].color, |
| paint->color_stops_scratch.arrayZ[1].color); |
| return res->add_object (std::move (obj)); |
| } |
| |
| /* Multiple stops: create sub-functions and stitch. */ |
| hb_vector_t<unsigned> sub_func_ids; |
| for (unsigned i = 0; i + 1 < count; i++) |
| { |
| hb_vector_t<char> sub; |
| hb_pdf_build_interpolation_function (&sub, |
| paint->color_stops_scratch.arrayZ[i].color, |
| paint->color_stops_scratch.arrayZ[i + 1].color); |
| sub_func_ids.push (res->add_object (std::move (sub))); |
| } |
| |
| /* Stitching function (Type 3). */ |
| hb_vector_t<char> obj; |
| hb_buf_append_str (&obj, "<< /FunctionType 3 /Domain [0 1]\n"); |
| |
| /* Functions array. */ |
| hb_buf_append_str (&obj, "/Functions ["); |
| for (unsigned i = 0; i < sub_func_ids.length; i++) |
| { |
| if (i) hb_buf_append_c (&obj, ' '); |
| hb_buf_append_unsigned (&obj, sub_func_ids.arrayZ[i]); |
| hb_buf_append_str (&obj, " 0 R"); |
| } |
| hb_buf_append_str (&obj, "]\n"); |
| |
| /* Bounds. */ |
| hb_buf_append_str (&obj, "/Bounds ["); |
| for (unsigned i = 1; i + 1 < count; i++) |
| { |
| if (i > 1) hb_buf_append_c (&obj, ' '); |
| hb_buf_append_num (&obj, paint->color_stops_scratch.arrayZ[i].offset, 4); |
| } |
| hb_buf_append_str (&obj, "]\n"); |
| |
| /* Encode array. */ |
| hb_buf_append_str (&obj, "/Encode ["); |
| for (unsigned i = 0; i + 1 < count; i++) |
| { |
| if (i) hb_buf_append_c (&obj, ' '); |
| hb_buf_append_str (&obj, "0 1"); |
| } |
| hb_buf_append_str (&obj, "] >>"); |
| |
| return res->add_object (std::move (obj)); |
| } |
| |
| static void |
| hb_pdf_paint_linear_gradient (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_color_line_t *color_line, |
| float x0, float y0, |
| float x1, float y1, |
| float x2 HB_UNUSED, float y2 HB_UNUSED, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| auto *res = hb_pdf_get_resources (paint); |
| if (unlikely (!res)) |
| return; |
| |
| unsigned func_id = hb_pdf_build_gradient_function (res, paint, color_line); |
| |
| /* Build Type 2 (axial) shading. */ |
| hb_vector_t<char> sh; |
| hb_buf_append_str (&sh, "<< /ShadingType 2 /ColorSpace /DeviceRGB\n"); |
| hb_buf_append_str (&sh, "/Coords ["); |
| hb_buf_append_num (&sh, x0, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, y0, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, x1, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, y1, paint->precision); |
| hb_buf_append_str (&sh, "]\n/Function "); |
| hb_buf_append_unsigned (&sh, func_id); |
| hb_buf_append_str (&sh, " 0 R\n"); |
| |
| hb_paint_extend_t extend = hb_color_line_get_extend (color_line); |
| if (extend == HB_PAINT_EXTEND_PAD) |
| hb_buf_append_str (&sh, "/Extend [true true]\n"); |
| |
| hb_buf_append_str (&sh, ">>"); |
| |
| unsigned sh_idx = res->add_shading (std::move (sh)); |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "/SH"); |
| hb_buf_append_unsigned (&body, sh_idx); |
| hb_buf_append_str (&body, " sh\n"); |
| } |
| |
| static void |
| hb_pdf_paint_radial_gradient (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_color_line_t *color_line, |
| float x0, float y0, float r0, |
| float x1, float y1, float r1, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| auto *res = hb_pdf_get_resources (paint); |
| if (unlikely (!res)) |
| return; |
| |
| unsigned func_id = hb_pdf_build_gradient_function (res, paint, color_line); |
| |
| /* Build Type 3 (radial) shading. */ |
| hb_vector_t<char> sh; |
| hb_buf_append_str (&sh, "<< /ShadingType 3 /ColorSpace /DeviceRGB\n"); |
| hb_buf_append_str (&sh, "/Coords ["); |
| hb_buf_append_num (&sh, x0, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, y0, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, r0, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, x1, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, y1, paint->precision); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, r1, paint->precision); |
| hb_buf_append_str (&sh, "]\n/Function "); |
| hb_buf_append_unsigned (&sh, func_id); |
| hb_buf_append_str (&sh, " 0 R\n"); |
| |
| hb_paint_extend_t extend = hb_color_line_get_extend (color_line); |
| if (extend == HB_PAINT_EXTEND_PAD) |
| hb_buf_append_str (&sh, "/Extend [true true]\n"); |
| |
| hb_buf_append_str (&sh, ">>"); |
| |
| unsigned sh_idx = res->add_shading (std::move (sh)); |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "/SH"); |
| hb_buf_append_unsigned (&body, sh_idx); |
| hb_buf_append_str (&body, " sh\n"); |
| } |
| |
| |
| /* Encode a 16-bit big-endian unsigned value into buf. */ |
| static void |
| hb_pdf_encode_u16 (hb_vector_t<char> *buf, uint16_t v) |
| { |
| char bytes[2] = {(char) (v >> 8), (char) (v & 0xFF)}; |
| hb_buf_append_len (buf, bytes, 2); |
| } |
| |
| /* Encode a coordinate as 16-bit value relative to Decode range. */ |
| static void |
| hb_pdf_encode_coord (hb_vector_t<char> *buf, |
| float val, float lo, float hi) |
| { |
| float t = (val - lo) / (hi - lo); |
| t = hb_clamp (t, 0.f, 1.f); |
| hb_pdf_encode_u16 (buf, (uint16_t) (t * 65535.f + 0.5f)); |
| } |
| |
| /* Encode RGB from hb_color_t as 3 bytes. */ |
| static void |
| hb_pdf_encode_color_rgb (hb_vector_t<char> *buf, hb_color_t c) |
| { |
| char rgb[3] = {(char) hb_color_get_red (c), |
| (char) hb_color_get_green (c), |
| (char) hb_color_get_blue (c)}; |
| hb_buf_append_len (buf, rgb, 3); |
| } |
| |
| /* Encode one Coons patch control point. */ |
| static void |
| hb_pdf_encode_point (hb_vector_t<char> *buf, |
| float x, float y, |
| float xlo, float xhi, |
| float ylo, float yhi) |
| { |
| hb_pdf_encode_coord (buf, x, xlo, xhi); |
| hb_pdf_encode_coord (buf, y, ylo, yhi); |
| } |
| |
| /* Emit one Coons patch sector into the mesh stream. |
| * Splits large arcs into sub-patches of max 90°. */ |
| static void |
| hb_pdf_add_sweep_patch (hb_vector_t<char> *mesh, |
| float cx, float cy, |
| float xlo, float xhi, float ylo, float yhi, |
| float a0, hb_color_t c0_in, |
| float a1, hb_color_t c1_in) |
| { |
| const float R = 32767.f; |
| const float eps = 0.5f; |
| const float MAX_SECTOR = (float) M_PI / 2.f; |
| |
| int num_splits = (int) ceilf (fabsf (a1 - a0) / MAX_SECTOR); |
| if (num_splits < 1) num_splits = 1; |
| |
| for (int s = 0; s < num_splits; s++) |
| { |
| float k0 = (float) s / num_splits; |
| float k1 = (float) (s + 1) / num_splits; |
| float sa0 = a0 + k0 * (a1 - a0); |
| float sa1 = a0 + k1 * (a1 - a0); |
| hb_color_t sc0 = hb_color_lerp (c0_in, c1_in, k0); |
| hb_color_t sc1 = hb_color_lerp (c0_in, c1_in, k1); |
| |
| float da = sa1 - sa0; |
| float kappa = (4.f / 3.f) * tanf (da / 4.f); |
| |
| float cos0 = cosf (sa0), sin0 = sinf (sa0); |
| float cos1 = cosf (sa1), sin1 = sinf (sa1); |
| |
| float p0x = cx + eps * cos0, p0y = cy + eps * sin0; |
| float p3x = cx + R * cos0, p3y = cy + R * sin0; |
| float p6x = cx + R * cos1, p6y = cy + R * sin1; |
| float p9x = cx + eps * cos1, p9y = cy + eps * sin1; |
| |
| /* Edge 1: p0→p3, radial straight line. */ |
| float e1_1x = p0x + (p3x - p0x) / 3.f; |
| float e1_1y = p0y + (p3y - p0y) / 3.f; |
| float e1_2x = p0x + 2.f * (p3x - p0x) / 3.f; |
| float e1_2y = p0y + 2.f * (p3y - p0y) / 3.f; |
| |
| /* Edge 2: p3→p6, outer arc. */ |
| float e2_1x = p3x + kappa * R * (-sin0); |
| float e2_1y = p3y + kappa * R * ( cos0); |
| float e2_2x = p6x - kappa * R * (-sin1); |
| float e2_2y = p6y - kappa * R * ( cos1); |
| |
| /* Edge 3: p6→p9, radial straight line. */ |
| float e3_1x = p6x + (p9x - p6x) / 3.f; |
| float e3_1y = p6y + (p9y - p6y) / 3.f; |
| float e3_2x = p6x + 2.f * (p9x - p6x) / 3.f; |
| float e3_2y = p6y + 2.f * (p9y - p6y) / 3.f; |
| |
| /* Edge 4: p9→p0, inner arc. */ |
| float e4_1x = p9x + kappa * eps * (-sin1); |
| float e4_1y = p9y + kappa * eps * ( cos1); |
| float e4_2x = p0x - kappa * eps * (-sin0); |
| float e4_2y = p0y - kappa * eps * ( cos0); |
| |
| hb_buf_append_c (mesh, '\0'); /* flag = 0, new patch */ |
| |
| hb_pdf_encode_point (mesh, p0x, p0y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e1_1x, e1_1y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e1_2x, e1_2y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, p3x, p3y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e2_1x, e2_1y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e2_2x, e2_2y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, p6x, p6y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e3_1x, e3_1y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e3_2x, e3_2y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, p9x, p9y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e4_1x, e4_1y, xlo, xhi, ylo, yhi); |
| hb_pdf_encode_point (mesh, e4_2x, e4_2y, xlo, xhi, ylo, yhi); |
| |
| hb_pdf_encode_color_rgb (mesh, sc0); /* inner start */ |
| hb_pdf_encode_color_rgb (mesh, sc0); /* outer start */ |
| hb_pdf_encode_color_rgb (mesh, sc1); /* outer end */ |
| hb_pdf_encode_color_rgb (mesh, sc1); /* inner end */ |
| } |
| } |
| |
| static void |
| hb_pdf_paint_sweep_gradient (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_color_line_t *color_line, |
| float cx, float cy, |
| float start_angle, |
| float end_angle, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| auto *res = hb_pdf_get_resources (paint); |
| if (unlikely (!res)) |
| return; |
| |
| /* Get and sort color stops. */ |
| unsigned n_stops = hb_color_line_get_color_stops (color_line, 0, nullptr, nullptr); |
| paint->color_stops_scratch.resize (n_stops); |
| hb_color_line_get_color_stops (color_line, 0, &n_stops, paint->color_stops_scratch.arrayZ); |
| paint->color_stops_scratch.as_array ().qsort ( |
| [] (const hb_color_stop_t &a, const hb_color_stop_t &b) |
| { return a.offset < b.offset; }); |
| hb_color_stop_t *stops = paint->color_stops_scratch.arrayZ; |
| |
| if (!n_stops) |
| return; |
| |
| hb_paint_extend_t extend = hb_color_line_get_extend (color_line); |
| |
| const float R = 32767.f; |
| float xlo = cx - R - 1, xhi = cx + R + 1; |
| float ylo = cy - R - 1, yhi = cy + R + 1; |
| |
| hb_vector_t<char> mesh; |
| mesh.alloc (256); |
| |
| hb_sweep_gradient_tiles (stops, n_stops, extend, |
| start_angle, end_angle, |
| [&] (float a0, hb_color_t c0, float a1, hb_color_t c1) |
| { hb_pdf_add_sweep_patch (&mesh, cx, cy, xlo, xhi, ylo, yhi, |
| a0, c0, a1, c1); }); |
| |
| if (!mesh.length) |
| return; |
| |
| /* Build the shading stream object. */ |
| hb_vector_t<char> sh; |
| hb_buf_append_str (&sh, "<< /ShadingType 6 /ColorSpace /DeviceRGB\n"); |
| hb_buf_append_str (&sh, "/BitsPerCoordinate 16 /BitsPerComponent 8 /BitsPerFlag 8\n"); |
| hb_buf_append_str (&sh, "/Decode ["); |
| hb_buf_append_num (&sh, xlo, 2); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, xhi, 2); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, ylo, 2); |
| hb_buf_append_c (&sh, ' '); |
| hb_buf_append_num (&sh, yhi, 2); |
| hb_buf_append_str (&sh, " 0 1 0 1 0 1]\n"); |
| hb_buf_append_str (&sh, "/Length "); |
| hb_buf_append_unsigned (&sh, mesh.length); |
| hb_buf_append_str (&sh, " >>\nstream\n"); |
| hb_buf_append_len (&sh, mesh.arrayZ, mesh.length); |
| hb_buf_append_str (&sh, "\nendstream"); |
| |
| unsigned sh_idx = res->add_shading (std::move (sh)); |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "/SH"); |
| hb_buf_append_unsigned (&body, sh_idx); |
| hb_buf_append_str (&body, " sh\n"); |
| } |
| |
| static const char * |
| hb_pdf_blend_mode_name (hb_paint_composite_mode_t mode) |
| { |
| switch (mode) |
| { |
| case HB_PAINT_COMPOSITE_MODE_MULTIPLY: return "Multiply"; |
| case HB_PAINT_COMPOSITE_MODE_SCREEN: return "Screen"; |
| case HB_PAINT_COMPOSITE_MODE_OVERLAY: return "Overlay"; |
| case HB_PAINT_COMPOSITE_MODE_DARKEN: return "Darken"; |
| case HB_PAINT_COMPOSITE_MODE_LIGHTEN: return "Lighten"; |
| case HB_PAINT_COMPOSITE_MODE_COLOR_DODGE: return "ColorDodge"; |
| case HB_PAINT_COMPOSITE_MODE_COLOR_BURN: return "ColorBurn"; |
| case HB_PAINT_COMPOSITE_MODE_HARD_LIGHT: return "HardLight"; |
| case HB_PAINT_COMPOSITE_MODE_SOFT_LIGHT: return "SoftLight"; |
| case HB_PAINT_COMPOSITE_MODE_DIFFERENCE: return "Difference"; |
| case HB_PAINT_COMPOSITE_MODE_EXCLUSION: return "Exclusion"; |
| case HB_PAINT_COMPOSITE_MODE_HSL_HUE: return "Hue"; |
| case HB_PAINT_COMPOSITE_MODE_HSL_SATURATION: return "Saturation"; |
| case HB_PAINT_COMPOSITE_MODE_HSL_COLOR: return "Color"; |
| case HB_PAINT_COMPOSITE_MODE_HSL_LUMINOSITY: return "Luminosity"; |
| case HB_PAINT_COMPOSITE_MODE_CLEAR: |
| case HB_PAINT_COMPOSITE_MODE_SRC: |
| case HB_PAINT_COMPOSITE_MODE_DEST: |
| case HB_PAINT_COMPOSITE_MODE_SRC_OVER: |
| case HB_PAINT_COMPOSITE_MODE_DEST_OVER: |
| case HB_PAINT_COMPOSITE_MODE_SRC_IN: |
| case HB_PAINT_COMPOSITE_MODE_DEST_IN: |
| case HB_PAINT_COMPOSITE_MODE_SRC_OUT: |
| case HB_PAINT_COMPOSITE_MODE_DEST_OUT: |
| case HB_PAINT_COMPOSITE_MODE_SRC_ATOP: |
| case HB_PAINT_COMPOSITE_MODE_DEST_ATOP: |
| case HB_PAINT_COMPOSITE_MODE_XOR: |
| case HB_PAINT_COMPOSITE_MODE_PLUS: |
| default: return nullptr; /* Normal / no PDF equivalent */ |
| } |
| } |
| |
| static void |
| hb_pdf_paint_push_group (hb_paint_funcs_t *, |
| void *paint_data, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| hb_buf_append_str (&paint->current_body (), "q\n"); |
| } |
| |
| static void |
| hb_pdf_paint_push_group_for (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_paint_composite_mode_t mode, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| |
| auto &body = paint->current_body (); |
| hb_buf_append_str (&body, "q\n"); |
| |
| const char *bm = hb_pdf_blend_mode_name (mode); |
| if (bm) |
| { |
| auto *res = hb_pdf_get_resources (paint); |
| if (likely (res)) |
| { |
| unsigned gs_idx = res->add_extgstate_blend (bm); |
| hb_buf_append_str (&body, "/GS"); |
| hb_buf_append_unsigned (&body, gs_idx); |
| hb_buf_append_str (&body, " gs\n"); |
| } |
| } |
| } |
| |
| static void |
| hb_pdf_paint_pop_group (hb_paint_funcs_t *, |
| void *paint_data, |
| hb_paint_composite_mode_t mode HB_UNUSED, |
| void *) |
| { |
| auto *paint = (hb_vector_paint_t *) paint_data; |
| if (unlikely (!hb_pdf_paint_ensure_initialized (paint))) |
| return; |
| hb_buf_append_str (&paint->current_body (), "Q\n"); |
| } |
| |
| |
| /* ---- lazy loader for paint funcs ---- */ |
| |
| static inline void free_static_pdf_paint_funcs (); |
| |
| static struct hb_pdf_paint_funcs_lazy_loader_t |
| : hb_paint_funcs_lazy_loader_t<hb_pdf_paint_funcs_lazy_loader_t> |
| { |
| static hb_paint_funcs_t *create () |
| { |
| hb_paint_funcs_t *funcs = hb_paint_funcs_create (); |
| hb_paint_funcs_set_push_transform_func (funcs, (hb_paint_push_transform_func_t) hb_pdf_paint_push_transform, nullptr, nullptr); |
| hb_paint_funcs_set_pop_transform_func (funcs, (hb_paint_pop_transform_func_t) hb_pdf_paint_pop_transform, nullptr, nullptr); |
| hb_paint_funcs_set_push_clip_glyph_func (funcs, (hb_paint_push_clip_glyph_func_t) hb_pdf_paint_push_clip_glyph, nullptr, nullptr); |
| hb_paint_funcs_set_push_clip_rectangle_func (funcs, (hb_paint_push_clip_rectangle_func_t) hb_pdf_paint_push_clip_rectangle, nullptr, nullptr); |
| hb_paint_funcs_set_pop_clip_func (funcs, (hb_paint_pop_clip_func_t) hb_pdf_paint_pop_clip, nullptr, nullptr); |
| hb_paint_funcs_set_color_func (funcs, (hb_paint_color_func_t) hb_pdf_paint_color, nullptr, nullptr); |
| hb_paint_funcs_set_image_func (funcs, (hb_paint_image_func_t) hb_pdf_paint_image, nullptr, nullptr); |
| hb_paint_funcs_set_linear_gradient_func (funcs, (hb_paint_linear_gradient_func_t) hb_pdf_paint_linear_gradient, nullptr, nullptr); |
| hb_paint_funcs_set_radial_gradient_func (funcs, (hb_paint_radial_gradient_func_t) hb_pdf_paint_radial_gradient, nullptr, nullptr); |
| hb_paint_funcs_set_sweep_gradient_func (funcs, (hb_paint_sweep_gradient_func_t) hb_pdf_paint_sweep_gradient, nullptr, nullptr); |
| hb_paint_funcs_set_push_group_func (funcs, (hb_paint_push_group_func_t) hb_pdf_paint_push_group, nullptr, nullptr); |
| hb_paint_funcs_set_push_group_for_func (funcs, (hb_paint_push_group_for_func_t) hb_pdf_paint_push_group_for, nullptr, nullptr); |
| hb_paint_funcs_set_pop_group_func (funcs, (hb_paint_pop_group_func_t) hb_pdf_paint_pop_group, nullptr, nullptr); |
| hb_paint_funcs_make_immutable (funcs); |
| hb_atexit (free_static_pdf_paint_funcs); |
| return funcs; |
| } |
| } static_pdf_paint_funcs; |
| |
| static inline void |
| free_static_pdf_paint_funcs () |
| { |
| static_pdf_paint_funcs.free_instance (); |
| } |
| |
| hb_paint_funcs_t * |
| hb_vector_paint_pdf_funcs_get () |
| { |
| return static_pdf_paint_funcs.get_unconst (); |
| } |
| |
| |
| /* ---- render ---- */ |
| |
| hb_blob_t * |
| hb_vector_paint_render_pdf (hb_vector_paint_t *paint) |
| { |
| if (!paint->has_extents) |
| return nullptr; |
| if (!paint->group_stack.length || |
| !paint->group_stack.arrayZ[0].length) |
| return nullptr; |
| |
| hb_vector_t<char> &content = paint->group_stack.arrayZ[0]; |
| hb_pdf_resources_t *res = hb_pdf_get_resources (paint); |
| |
| float ex = paint->extents.x; |
| float ey = paint->extents.y; |
| float ew = paint->extents.width; |
| float eh = paint->extents.height; |
| |
| unsigned num_extra = res ? res->objects.length : 0; |
| unsigned total_objects = 4 + num_extra; /* 1-based: 1..total_objects */ |
| |
| /* Build PDF. */ |
| hb_vector_t<char> out; |
| hb_buf_recover_recycled (paint->recycled_blob, &out); |
| out.alloc (content.length + num_extra * 128 + 1024); |
| |
| hb_vector_t<unsigned> offsets; |
| if (unlikely (!offsets.resize (total_objects))) |
| return nullptr; |
| |
| hb_buf_append_str (&out, "%PDF-1.4\n%\xC0\xC1\xC2\xC3\n"); |
| |
| /* Object 1: Catalog */ |
| offsets.arrayZ[0] = out.length; |
| hb_buf_append_str (&out, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"); |
| |
| /* Object 2: Pages */ |
| offsets.arrayZ[1] = out.length; |
| hb_buf_append_str (&out, "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"); |
| |
| /* Object 3: Page */ |
| offsets.arrayZ[2] = out.length; |
| hb_buf_append_str (&out, "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox ["); |
| hb_buf_append_num (&out, ex, paint->precision); |
| hb_buf_append_c (&out, ' '); |
| hb_buf_append_num (&out, -(ey + eh), paint->precision); |
| hb_buf_append_c (&out, ' '); |
| hb_buf_append_num (&out, ex + ew, paint->precision); |
| hb_buf_append_c (&out, ' '); |
| hb_buf_append_num (&out, -ey, paint->precision); |
| hb_buf_append_str (&out, "]\n/Contents 4 0 R"); |
| |
| /* Resources. */ |
| bool has_resources = res && |
| (res->extgstate_dict.length || res->shading_dict.length || res->xobject_dict.length); |
| if (has_resources) |
| { |
| hb_buf_append_str (&out, "\n/Resources <<"); |
| if (res->extgstate_dict.length) |
| { |
| hb_buf_append_str (&out, " /ExtGState << "); |
| hb_buf_append_len (&out, res->extgstate_dict.arrayZ, res->extgstate_dict.length); |
| hb_buf_append_str (&out, ">>"); |
| } |
| if (res->shading_dict.length) |
| { |
| hb_buf_append_str (&out, " /Shading << "); |
| hb_buf_append_len (&out, res->shading_dict.arrayZ, res->shading_dict.length); |
| hb_buf_append_str (&out, ">>"); |
| } |
| if (res->xobject_dict.length) |
| { |
| hb_buf_append_str (&out, " /XObject << "); |
| hb_buf_append_len (&out, res->xobject_dict.arrayZ, res->xobject_dict.length); |
| hb_buf_append_str (&out, ">>"); |
| } |
| hb_buf_append_str (&out, " >>"); |
| } |
| |
| hb_buf_append_str (&out, " >>\nendobj\n"); |
| |
| /* Object 4: Content stream */ |
| offsets.arrayZ[3] = out.length; |
| hb_buf_append_str (&out, "4 0 obj\n<< /Length "); |
| hb_buf_append_unsigned (&out, content.length); |
| hb_buf_append_str (&out, " >>\nstream\n"); |
| hb_buf_append_len (&out, content.arrayZ, content.length); |
| hb_buf_append_str (&out, "endstream\nendobj\n"); |
| |
| /* Extra objects (functions, shadings, ExtGState). */ |
| for (unsigned i = 0; i < num_extra; i++) |
| { |
| offsets.arrayZ[4 + i] = out.length; |
| hb_buf_append_unsigned (&out, 5 + i); |
| hb_buf_append_str (&out, " 0 obj\n"); |
| auto &obj = res->objects.arrayZ[i]; |
| hb_buf_append_len (&out, obj.data.arrayZ, obj.data.length); |
| hb_buf_append_str (&out, "\nendobj\n"); |
| } |
| |
| /* Cross-reference table */ |
| unsigned xref_offset = out.length; |
| hb_buf_append_str (&out, "xref\n0 "); |
| hb_buf_append_unsigned (&out, total_objects + 1); |
| hb_buf_append_str (&out, "\n0000000000 65535 f \n"); |
| for (unsigned i = 0; i < total_objects; i++) |
| { |
| char tmp[21]; |
| snprintf (tmp, sizeof (tmp), "%010u 00000 n \n", offsets.arrayZ[i]); |
| hb_buf_append_len (&out, tmp, 20); |
| } |
| |
| /* Trailer */ |
| hb_buf_append_str (&out, "trailer\n<< /Size "); |
| hb_buf_append_unsigned (&out, total_objects + 1); |
| hb_buf_append_str (&out, " /Root 1 0 R >>\nstartxref\n"); |
| hb_buf_append_unsigned (&out, xref_offset); |
| hb_buf_append_str (&out, "\n%%EOF\n"); |
| |
| hb_blob_t *blob = hb_buf_blob_from (&paint->recycled_blob, &out); |
| |
| hb_vector_paint_clear (paint); |
| |
| return blob; |
| } |