Edit

kc3-lang/kc3/pdf/pdf_buf_write.c

Branch :

  • pdf/pdf_buf_write.c
  • /* kc3
     * Copyright from 2022 to 2026 kmx.io <contact@kmx.io>
     *
     * Permission is hereby granted to use this software granted the above
     * copyright notice and this permission paragraph are included in all
     * copies and substantial portions of this software.
     *
     * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
     * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
     * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
     * THIS SOFTWARE.
     */
    #include <math.h>
    #include "../libkc3/kc3.h"
    #include "pdf_buf_write.h"
    
    sw pdf_buf_write_array (s_buf *buf, const p_list src)
    {
      p_list n = src;
      sw r;
      sw result = 0;
      assert(src);
      if ((r = buf_write_1(buf, "[")) < 0) {
        return r;
      }
      result += r;
      while (n) {
        if ((r = pdf_buf_write_tag(buf, &n->tag)) < 0) {
          err_write_1("pdf_buf_write_array: pdf_buf_write_tag: ");
          err_inspect_tag(&n->tag);
          err_write_1("\n");
          return r;
        }
        result += r;
        n = list_next(n);
        if (n && (r = buf_write_1(buf, " ")) < 0)
          return r;
        result += r;
      }
      if ((r = buf_write_1(buf, "]")) < 0) {
        return r;
      }
      result += r;
      return result;
    }
    
    sw pdf_buf_write_bool (s_buf *buf, bool src)
    {
      if (src) {
        return buf_write_1(buf, "true");
      }
      return buf_write_1(buf, "false");
    }
    
    sw pdf_buf_write_dictionnary (s_buf *buf, const s_map *src)
    {
      uw i;
      sw r;
      sw result = 0;
      if ((r = buf_write_1(buf, "<<")) < 0) {
        return r;
      }
      result += r;
      for (i = 0; i < src->count; i++) {
        if (src->key[i].type != TAG_PSYM) {
          err_puts("pdf_buf_write_dictionary: key is not a Sym");
          assert(! "pdf_buf_write_dictionary: key is not a Sym");
          return -1;
        }
        if ((r = pdf_buf_write_name(buf, src->key[i].data.psym)) < 0)
          return r;
        result += r;
        if ((r = pdf_buf_write_separator(buf, false)) < 0)
          return r;
        result += r;
        if ((r = pdf_buf_write_tag(buf, &src->value[i])) < 0) {
          err_write_1("pdf_buf_write_dictionnary: pdf_buf_write_tag: key ");
          err_inspect_sym(src->key[i].data.psym);
          err_write_1(", value ");
          err_inspect_tag(&src->value[i]);
          err_write_1("\n");
          return r;
        }
        result += r;
        if (i < src->count - 1 &&
            (r = pdf_buf_write_separator(buf, true)) < 0)
          return r;
        result += r;
      }
      if ((r = pdf_buf_write_token(buf, ">>")) < 0) {
        return r;
      }
      result += r;
      return result;
    }
    
    sw pdf_buf_write_flat_array (s_buf *buf, const s_array *src)
    {
      uw i;
      sw r;
      sw result = 0;
      uw count = 0;
      f32 *value;
      assert(buf);
      assert(src);
      if (src->dimension_count != 1) {
        err_puts("pdf_buf_write_flat_array: array dimension count != 1");
        assert(! "pdf_buf_write_flat_array: array dimension count != 1");
        return -1;
      }
      count = src->dimensions[0].count;
      for (i = 0; i < count; i++) {
        if ((value = array_data(src, &i)) == NULL)
          return -1;
        if ((r = pdf_buf_write_float(buf, *value)) < 0)
          return r;
        result += r;
        if (i < count - 1 &&
            (r = pdf_buf_write_separator(buf, false)) < 0)
          return r;
        result += r;
      }
      return result;
    }
    
    sw pdf_buf_write_float (s_buf *buf, f32 src)
    {
      u8 d[64] = {0};
      u8 i;
      u8 n;
      sw r;
      sw result = 0;
      f32 x;
      f32 frac;
      assert(buf);
      x = src;
      if (x == 0.0f)
        return buf_write_1(buf, "0");
      if (x < 0.0f) {
        if ((r = buf_write_1(buf, "-")) < 0)
          return r;
        result += r;
        x = -x;
      }
      /* Extract integer part. */
      n = 0;
      frac = x - floorf(x);
      x = floorf(x);
      if (x >= 1.0f) {
        while (n < sizeof(d) && x >= 1.0f) {
          d[n] = (u8) fmodf(x, 10.0f);
          x = floorf(x / 10.0f);
          n++;
        }
        /* Write digits in reverse order. */
        i = n;
        while (i > 0) {
          i--;
          if ((r = buf_write_u8(buf, d[i] + '0')) < 0)
            return r;
          result += r;
        }
      }
      else {
        if ((r = buf_write_u8(buf, '0')) < 0)
          return r;
        result += r;
      }
      /* Write fractional part. */
      if (frac > 0.0f) {
        if ((r = buf_write_1(buf, ".")) < 0)
          return r;
        result += r;
        i = 0;
        while (frac > 0.0f && i < 6) {
          frac = frac * 10.0f;
          d[0] = (u8) floorf(frac);
          if ((r = buf_write_u8(buf, d[0] + '0')) < 0)
            return r;
          result += r;
          frac = frac - (f32) d[0];
          i++;
        }
      }
      return result;
    }
    
    sw pdf_buf_write_header (s_buf *buf)
    {
      sw r;
      sw result = 0;
      s_str str = STR("%PDF-1.4\r%\xe2\xe3\xcf\xd3\n");
      if ((r = buf_write_str(buf, &str)) < 0)
        return r;
      result += r;
      return result;
    }
    
    sw pdf_buf_write_indirect_ref (s_buf *buf, const s_tuple *tuple)
    {
      sw r = 0;
      sw result = 0;
      assert(buf);
      assert(tuple);
      if (tuple->count != 3 ||
          tuple->tag[0].type != TAG_PSYM ||
          tuple->tag[0].data.psym != sym_1("indirect_object")) {
        err_puts("pdf_buf_write_indirect_object: invalid Tuple");
        assert(! "pdf_buf_write_indirect_object: invalid Tuple");
        return -1;
      }
      if ((r = pdf_buf_write_tag(buf, tuple->tag + 1)) < 0) {
        return r;
      }
      result += r;
      if ((r = pdf_buf_write_separator(buf, false)) < 0) {
        return r;
      }
      result += r;
      if ((r = pdf_buf_write_tag(buf, tuple->tag + 2)) < 0) {
        return r;
      }
      result += r;
      if ((r = pdf_buf_write_token(buf, " R")) < 0)
        return r;
      result += r;
      return result;
    }
    
    sw pdf_buf_write_indirect_start (s_buf *buf, const s_tuple *src)
    {
      sw r = 0;
      sw result = 0;
      assert(buf);
      assert(src);
      if (src->count != 2) {
        err_puts("pdf_buf_write_indirect_object: invalid tuple size");
        assert(! "pdf_buf_write_indirect_object: invalid tuple size");
        return -1;
      }
      if ((r = pdf_buf_write_tag(buf, &src->tag[0])) < 0) {
        return r;
      }
      result += r;
      if ((r = pdf_buf_write_separator(buf, false)) < 0) {
        return r;
      }
      result += r;
      if ((r = pdf_buf_write_tag(buf, &src->tag[1])) < 0) {
        return r;
      }
      result += r;
      return result + pdf_buf_write_token_with_separator(buf, " obj", true);
    }
    
    sw pdf_buf_write_indirect_end (s_buf *buf)
    {
      sw r = 0;
      assert(buf);
      if ((r = pdf_buf_write_separator(buf, true)) < 0) {
        return r;
      }
      return r + pdf_buf_write_token_with_separator(buf, "endobj", true);
    }
    
    sw pdf_buf_write_integer (s_buf *buf, s32 src)
    {
      assert(buf);
      return buf_inspect_s32_decimal(buf, src);
    }
    
    sw pdf_buf_write_jpeg_xobject (s_buf *buf, u32 object_number,
                                   u32 width, u32 height,
                                   u32 num_components,
                                   FILE *fp, uw length)
    {
      size_t bytes_read;
      u8 chunk[BUF_SIZE];
      uw chunk_size;
      uw remaining;
      sw r;
      sw result = 0;
      const char *colorspace;
      assert(buf);
      colorspace = (num_components == 1) ? "/DeviceGray" :
                   (num_components == 3) ? "/DeviceRGB" :
                   (num_components == 4) ? "/DeviceCMYK" : "/DeviceRGB";
      if ((r = buf_inspect_u32_decimal(buf, object_number)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, " 0 obj\n")) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "<<\n/Type /XObject\n"
                           "/Subtype /Image\n")) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "/Width ")) < 0)
        return r;
      result += r;
      if ((r = buf_inspect_u32_decimal(buf, width)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "\n/Height ")) < 0)
        return r;
      result += r;
      if ((r = buf_inspect_u32_decimal(buf, height)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "\n/ColorSpace ")) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, colorspace)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "\n/BitsPerComponent 8\n"
                           "/Filter /DCTDecode\n"
                           "/Length ")) < 0)
        return r;
      result += r;
      if ((r = buf_inspect_uw_decimal(buf, length)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "\n>>\nstream\n")) < 0)
        return r;
      result += r;
      remaining = length;
      while (remaining > 0) {
        chunk_size = remaining > sizeof(chunk) ? sizeof(chunk) : remaining;
        bytes_read = fread(chunk, 1, chunk_size, fp);
        if (bytes_read != chunk_size) {
          err_puts("kc3_pdf_write_jpeg_xobject: fread");
          return -1;
        }
        if ((r = buf_flush(buf)) < 0) {
          err_puts("kc3_pdf_write_jpeg_xobject: buf_flush");
          return -1;
        }
        if ((r = buf_write(buf, chunk, chunk_size)) < 0) {
          err_puts("kc3_pdf_write_jpeg_xobject: buf_write");
          return -1;
        }
        result += r;
        remaining -= chunk_size;
      }
      if ((r = buf_write_1(buf, "\nendstream\nendobj\n")) < 0)
        return r;
      result += r;
      return result;
    }
    
    sw pdf_buf_write_name (s_buf *buf, p_pdf_name src)
    {
      sw r = 0;
      if ((r = pdf_buf_write_token(buf, "/")) < 0) {
        return r;
      }
      return r += buf_write_str(buf, &src->str);
    }
    
    sw pdf_buf_write_null (s_buf *buf)
    {
        return pdf_buf_write_token(buf, "null");
    }
    
    sw pdf_buf_write_rect (s_buf *buf, s_pdf_rect *rect)
    {
      sw r;
      sw result = 0;
      assert(rect);
      if ((r = buf_write_1(buf, "[")) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_float(buf, rect->x)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, " ")) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_float(buf, rect->y)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, " ")) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_float(buf, rect->w)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, " ")) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_float(buf, rect->h)) < 0)
        return r;
      result += r;
      if ((r = buf_write_1(buf, "]")) < 0)
        return r;
      result += r;
      return result;
    }
    
    sw pdf_buf_write_separator(s_buf *buf, bool newline)
    {
      return buf_write_1(buf, newline ? "\n" : " ");
    }
    
    sw pdf_buf_write_string_hex (s_buf *buf, const s_str *str)
    {
      u8 c;
      u32 i;
      sw r = 0;
      sw result = 0;
      s_buf read_buf = {0};
      const char *base = g_kc3_base_hexadecimal.ptr.pchar;
      buf_init_str_const(&read_buf, str);
      if ((r = pdf_buf_write_token(buf, "<")) < 0)
        goto cleanup;
      result += r;
      for (i = 0; i < str->size; i++) {
        if ((r = buf_read_u8(&read_buf, &c)) < 0
          || (r = buf_write_u8(buf, base[c & 0xf])) < 0
          || (r = buf_write_u8(buf, base[(c >> 4) & 0xf])) < 0)
          goto cleanup;
        result += 2;
      }
      if ((r = pdf_buf_write_token(buf, ">")) < 0)
        goto cleanup;
      result += r;
     cleanup:
      buf_clean(&read_buf);
      return result;
    }
    
    sw pdf_buf_write_tag (s_buf *buf, const s_tag *src)
    {
      assert(buf);
      assert(src);
      switch (src->type) {
        case TAG_F32:  return pdf_buf_write_float(buf, src->data.f32);
        case TAG_F64:  return pdf_buf_write_float(buf, src->data.f64);
        case TAG_SW:   return pdf_buf_write_integer(buf, src->data.sw);
        case TAG_S64:  return pdf_buf_write_integer(buf, src->data.s64);
        case TAG_S32:  return pdf_buf_write_integer(buf, src->data.s32);
        case TAG_S16:  return pdf_buf_write_integer(buf, src->data.s16);
        case TAG_S8:   return pdf_buf_write_integer(buf, src->data.s8);
        case TAG_U8:   return pdf_buf_write_integer(buf, src->data.u8);
        case TAG_U16:  return pdf_buf_write_integer(buf, src->data.u16);
        case TAG_U32:  return pdf_buf_write_integer(buf, src->data.u32);
        case TAG_U64:  return pdf_buf_write_integer(buf, src->data.u64);
        case TAG_UW:   return pdf_buf_write_integer(buf, src->data.uw);
        case TAG_INTEGER:
          return buf_inspect_integer(buf, &src->data.integer);
        case TAG_MAP:
          return pdf_buf_write_dictionnary(buf, &src->data.map);
        case TAG_STR:  return pdf_buf_write_string_hex(buf, &src->data.str);
        case TAG_PLIST:
          return pdf_buf_write_array(buf, src->data.plist);
        case TAG_PSYM: return pdf_buf_write_name(buf, src->data.psym);
        case TAG_PSTRUCT:
          if (src->data.pstruct->pstruct_type->module == sym_1("PDF.Rect"))
            return pdf_buf_write_rect(buf,
              (s_pdf_rect *) src->data.pstruct->data);
          err_write_1("pdf_buf_write_tag: unsupported struct type: ");
          err_inspect_sym(src->data.pstruct->pstruct_type->module);
          err_write_1("\n");
          assert(! "pdf_buf_write_tag: unsupported struct type");
          return -1;
        case TAG_PTUPLE:
          if (src->data.ptuple->count == 3 &&
              src->data.ptuple->tag[0].type == TAG_PSYM &&
              src->data.ptuple->tag[0].data.psym == sym_1("indirect_object"))
            return pdf_buf_write_indirect_ref(buf, src->data.ptuple);
          goto invalid_type;
        default:
          goto invalid_type;
      }
      err_puts("pdf_buf_write_tag: unknown tag type");
      assert(! "pdf_buf_write_tag: unknown tag type");
      return -1;
     invalid_type:
      err_write_1("pdf_buf_write_tag: invalid PDF tag type: ");
      err_puts(tag_type_to_string(src->type));
      assert(! "pdf_buf_write_tag: invalid PDF tag type");
      return -1;
    }
    
    sw pdf_buf_write_token (s_buf *buf, const char *pchar)
    {
      assert(buf);
      return buf_write_1(buf, pchar);
    }
    
    sw pdf_buf_write_token_with_separator (s_buf *buf, const char *pchar,
                                           bool newline)
    {
      sw r;
      sw result = 0;
      assert(buf);
      if ((r = pdf_buf_write_token(buf, pchar)) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_separator(buf, newline)) < 0)
        return r;
      result += r;
      return result;
    }
    
    sw pdf_buf_write_vec2 (s_buf *buf, const s_vec2 *src)
    {
      assert(buf);
      assert(src);
      sw r;
      sw result = 0;
      if ((r = pdf_buf_write_float(buf, src->x)) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_separator(buf, false)) < 0)
        return r;
      result += r;
      if ((r = pdf_buf_write_float(buf, src->y)) < 0)
        return r;
      result += r;
      return result;
    }