# typed: false
# coding: utf-8

describe PDF::Reader::CMap do

  describe "initialisation" do
    context "cmap with bfchar" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_bfchar.txt"
        map = PDF::Reader::CMap.new(binread(filename))
        expect(map.map).to be_a_kind_of(Hash)
        expect(map.size).to     eq(9)
        expect(map.map[0x1]).to eq([0x48])
        expect(map.map[0x2]).to eq([0x65])
        expect(map.map[0x9]).to eq([0x73])
      end
    end

    context "cmap with bfchar that converts a charatcer to a unicode codepoint" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_bfchar.txt"
        map = PDF::Reader::CMap.new(binread(filename))
        expect(map.decode(0x1)).to eq([0x48])
        expect(map.decode(0x2)).to eq([0x65])
        expect(map.decode(0x9)).to eq([0x73])
      end
    end

    context "cmap with bfchar and bfrange" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_bfrange.txt"
        map = PDF::Reader::CMap.new(binread(filename))
        expect(map.decode(0x16C9)).to eq([0x4F38]) # mapped with the bfchar operator
        expect(map.decode(0x0003)).to eq([0x0020]) # mapped with the bfrange operator
        expect(map.decode(0x0004)).to eq([0x0020+1]) # mapped with the bfrange operator
        expect(map.decode(0x0005)).to eq([0x0020+2]) # mapped with the bfrange operator
      end
    end

    context "cmap with bfrange and some indentation" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_bfrange_two.txt"
        map = PDF::Reader::CMap.new(binread(filename))
        expect(map.decode(0x0100)).to eq([0x0100]) # mapped with the bfrange operator
        expect(map.decode(0xDE00)).to eq([0xDE00]) # mapped with the bfrange operator
      end
    end

    context "cmap with array version of bfrange" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_bfrange_three.txt"
        map = PDF::Reader::CMap.new(binread(filename))

        expect(map.size).to eql(256)
        expect(map.decode(0x00)).to eq([0xfffd]) # mapped with the bfrange operator
        expect(map.decode(0x01)).to eq([0x0050]) # mapped with the bfrange operator
        expect(map.decode(0x03)).to eq([0x0067]) # mapped with the bfrange operator
        expect(map.decode(0x08)).to eq([0x0073]) # mapped with the bfrange operator
      end
    end

    context "when endbfrange is not on a dedicated line" do
      it "correctly loads a cmap" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_bfrange_four.txt"
        map = PDF::Reader::CMap.new(binread(filename))

        expect(map.size).to eql(3)
        expect(map.decode(0x61)).to eq([0x61]) # mapped with the bfrange operator
        expect(map.decode(0x62)).to eq([0x62]) # mapped with the bfrange operator
        expect(map.decode(0x63)).to eq([0x63]) # mapped with the bfrange operator
      end
    end

    context "cmap with bfchar and ligatures" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_ligatures.txt"
        map = PDF::Reader::CMap.new(binread(filename))

        expect(map.decode(0x00B7)).to eql([0x2019])
        expect(map.decode(0x00C0)).to eql([0x66, 0x69])
        expect(map.decode(0x00C1)).to eql([0x66, 0x6C])
      end
    end

    context "cmap with bfchar and surrogate pairs" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_surrogate_pairs.txt"
        map = PDF::Reader::CMap.new(binread(filename))

        expect(map.decode(0x0502)).to eql([0x03D1])
        expect(map.decode(0x0C09)).to eql([0x1D6FD])
        expect(map.decode(0x0723)).to eql([0x1D434])
        expect(map.decode(0x0C23)).to eql([0x1D717])
        expect(map.decode(0x0526)).to eql([0x20D7])
        expect(map.decode(0x072B)).to eql([0x1D43C])
        expect(map.decode(0x122C)).to eql([0xFFFD])
      end
    end

    context "cmap with bfchar with surrogate pairs and ligatures" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_multiple_surrogate_pairs.txt"
        map = PDF::Reader::CMap.new(binread(filename))

        expect(map.decode(0x0BC9)).to eql([0x1D443, 0x1D443])
      end
    end

    context "cmap with bfrange containing > 255 characters" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_large_bfrange.txt"
        map = PDF::Reader::CMap.new(binread(filename))

        expect(map.decode(0x00B7)).to eql([0x00B7])
        expect(map.decode(0x00C0)).to eql([0x00C0])
        expect(map.decode(0x00C1)).to eql([0x00C1])
      end
    end

    context "cmap with bfchar and surrogate pairs, where the surrogate pair starts with D800" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_surrogate_pairs_on_boundary.txt"
        map = PDF::Reader::CMap.new(binread(filename))
        expect(map.map).to be_a_kind_of(Hash)
        expect(map.size).to        eq(27)
        expect(map.map[0x0]).to    eq([0x10102])
        expect(map.map[0xB]).to    eq([0x28])
        expect(map.map[0x1E]).to   eq([0x3B])
        expect(map.map[0x0194]).to eq([0x25CF])
      end
    end

    context "cmap with bfrange and surrogate pairs, where the surrogate pair starts with D800" do
      it "skips ranges that start with an incomplete surrogate pair" do
        cmap_data = <<-CMAP
          /CIDInit /ProcSet findresource begin
          12 dict begin
          begincmap
          1 beginbfrange
          <0001> <0002> <D800>
          endbfrange
          endcmap
          CMapName currentdict /CMap defineresource pop
          end
          end
        CMAP

        map = PDF::Reader::CMap.new(cmap_data)
        expect(map.size).to eq(0)
        expect(map.decode(0x0001)).to eq([])
        expect(map.decode(0x0002)).to eq([])
      end
    end

    context "cmap with bfchar and a surrogate codepoint that's missing its pair" do
      it "correctly loads character mapping" do
        filename = File.dirname(__FILE__) + "/../data/cmap_with_invalid_surrogate_pair.txt"
        map = PDF::Reader::CMap.new(binread(filename))
        expect(map.map).to be_a_kind_of(Hash)
        expect(map.size).to         eq(17)
        expect(map.map[0x6]).to     eq([0x43])
        expect(map.map[0x01E1]).to  eq([0x2C])
        expect(map.map[0x0AC9]).to  be_nil
      end
    end

    describe "bfrange type one with a nil start code" do
      it "skips the range" do
        cmap_data = <<-CMAP
          /CIDInit /ProcSet findresource begin
          12 dict begin
          begincmap
          1 beginbfrange
          <> <0003> <1234>
          endbfrange
          endcmap
          CMapName currentdict /CMap defineresource pop
          end
          end
        CMAP

        map = PDF::Reader::CMap.new(cmap_data)
        expect(map.size).to eq(0)
      end
    end

    describe "bfrange type one with a nil end code" do
      it "skips the range" do
        cmap_data = <<-CMAP
          /CIDInit /ProcSet findresource begin
          12 dict begin
          begincmap
          1 beginbfrange
          <0001> <> <1234>
          endbfrange
          endcmap
          CMapName currentdict /CMap defineresource pop
          end
          end
        CMAP

        map = PDF::Reader::CMap.new(cmap_data)
        expect(map.size).to eq(0)
      end
    end

    describe "bfrange type two where the array is shorter than the range" do
      it "maps the parts of the range that are fouind in the array, skips the rest" do
        cmap_data = <<-CMAP
          /CIDInit /ProcSet findresource begin
          12 dict begin
          begincmap
          1 beginbfrange
          <0001> <0005> [<0041> <0042>]
          endbfrange
          endcmap
          CMapName currentdict /CMap defineresource pop
          end
          end
        CMAP

        map = PDF::Reader::CMap.new(cmap_data)
        expect(map.decode(0x0001)).to eq([0x0041])
        expect(map.decode(0x0002)).to eq([0x0042])
        expect(map.decode(0x0003)).to eq([])
        expect(map.decode(0x0004)).to eq([])
        expect(map.decode(0x0005)).to eq([])
      end
    end

    context "cmap with PostScript def keywords inside dictionary blocks" do
      it "correctly loads character mapping" do
        cmap_data = <<-CMAP
          /CIDInit /ProcSet findresource begin
          12 dict begin
          begincmap
          /CIDSystemInfo << /Registry (TestFont+0) def /Ordering (T1UV) def /Supplement 0 def >> def
          /CMapName /TestFont+0 def
          1 begincodespacerange
          <00> <FF>
          endcodespacerange
          3 beginbfchar
          <01> <0041>
          <02> <0042>
          <03> <0043>
          endbfchar
          endcmap
        CMAP

        map = PDF::Reader::CMap.new(cmap_data)
        expect(map.size).to eq(3)
        expect(map.decode(0x01)).to eq([0x0041])
        expect(map.decode(0x02)).to eq([0x0042])
        expect(map.decode(0x03)).to eq([0x0043])
      end
    end

    describe "initialized with an empty string" do
      it "inits without error but has no mappings and decode returns empty arrays" do
        map = PDF::Reader::CMap.new("")
        expect(map.size).to eq(0)
        expect(map.decode(1)).to eq([])
      end

      it "returns empty array for nil input" do
        map = PDF::Reader::CMap.new("")
        expect(map.decode(nil)).to eq([])
      end
    end

    context "initialized with malformed cmap data" do
      it "gracefully handles invalid data without crashing" do
        invalid_cmap = <<-CMAP
          /CIDInit /ProcSet findresource begin
          12 dict begin
          begincmap
          1 beginbfrange
          123 456 789
          endbfrange
          endcmap
          CMapName currentdict /CMap defineresource pop
          end
          end
        CMAP

        # Should not crash, may silently ignore invalid data
        expect {
          PDF::Reader::CMap.new(invalid_cmap)
        }.not_to raise_error
      end
    end
  end
end
