| // Copyright 2017 The PDFium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com |
| |
| #include "core/fpdfapi/font/cpdf_cmap.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "core/fpdfapi/cmaps/fpdf_cmaps.h" |
| #include "core/fpdfapi/font/cpdf_cmapparser.h" |
| #include "core/fpdfapi/font/cpdf_fontglobals.h" |
| #include "core/fpdfapi/parser/cpdf_simple_parser.h" |
| #include "third_party/base/check.h" |
| |
| namespace { |
| |
| struct ByteRange { |
| uint8_t m_First; |
| uint8_t m_Last; // Inclusive. |
| }; |
| |
| struct PredefinedCMap { |
| const char* m_pName; // Raw, POD struct. |
| CIDSet m_Charset; |
| CIDCoding m_Coding; |
| CPDF_CMap::CodingScheme m_CodingScheme; |
| uint8_t m_LeadingSegCount; |
| ByteRange m_LeadingSegs[2]; |
| }; |
| |
| constexpr PredefinedCMap kPredefinedCMaps[] = { |
| {"GB-EUC", |
| CIDSET_GB1, |
| CIDCoding::kGB, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfe}}}, |
| {"GBpc-EUC", |
| CIDSET_GB1, |
| CIDCoding::kGB, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfc}}}, |
| {"GBK-EUC", |
| CIDSET_GB1, |
| CIDCoding::kGB, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x81, 0xfe}}}, |
| {"GBKp-EUC", |
| CIDSET_GB1, |
| CIDCoding::kGB, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x81, 0xfe}}}, |
| {"GBK2K-EUC", |
| CIDSET_GB1, |
| CIDCoding::kGB, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x81, 0xfe}}}, |
| {"GBK2K", |
| CIDSET_GB1, |
| CIDCoding::kGB, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x81, 0xfe}}}, |
| {"UniGB-UCS2", CIDSET_GB1, CIDCoding::kUCS2, CPDF_CMap::TwoBytes, 0, {}}, |
| {"UniGB-UTF16", CIDSET_GB1, CIDCoding::kUTF16, CPDF_CMap::TwoBytes, 0, {}}, |
| {"B5pc", |
| CIDSET_CNS1, |
| CIDCoding::kBIG5, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfc}}}, |
| {"HKscs-B5", |
| CIDSET_CNS1, |
| CIDCoding::kBIG5, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x88, 0xfe}}}, |
| {"ETen-B5", |
| CIDSET_CNS1, |
| CIDCoding::kBIG5, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfe}}}, |
| {"ETenms-B5", |
| CIDSET_CNS1, |
| CIDCoding::kBIG5, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfe}}}, |
| {"UniCNS-UCS2", CIDSET_CNS1, CIDCoding::kUCS2, CPDF_CMap::TwoBytes, 0, {}}, |
| {"UniCNS-UTF16", |
| CIDSET_CNS1, |
| CIDCoding::kUTF16, |
| CPDF_CMap::TwoBytes, |
| 0, |
| {}}, |
| {"83pv-RKSJ", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x81, 0x9f}, {0xe0, 0xfc}}}, |
| {"90ms-RKSJ", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x81, 0x9f}, {0xe0, 0xfc}}}, |
| {"90msp-RKSJ", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x81, 0x9f}, {0xe0, 0xfc}}}, |
| {"90pv-RKSJ", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x81, 0x9f}, {0xe0, 0xfc}}}, |
| {"Add-RKSJ", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x81, 0x9f}, {0xe0, 0xfc}}}, |
| {"EUC", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x8e, 0x8e}, {0xa1, 0xfe}}}, |
| {"H", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::TwoBytes, |
| 1, |
| {{0x21, 0x7e}}}, |
| {"V", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::TwoBytes, |
| 1, |
| {{0x21, 0x7e}}}, |
| {"Ext-RKSJ", |
| CIDSET_JAPAN1, |
| CIDCoding::kJIS, |
| CPDF_CMap::MixedTwoBytes, |
| 2, |
| {{0x81, 0x9f}, {0xe0, 0xfc}}}, |
| {"UniJIS-UCS2", |
| CIDSET_JAPAN1, |
| CIDCoding::kUCS2, |
| CPDF_CMap::TwoBytes, |
| 0, |
| {}}, |
| {"UniJIS-UCS2-HW", |
| CIDSET_JAPAN1, |
| CIDCoding::kUCS2, |
| CPDF_CMap::TwoBytes, |
| 0, |
| {}}, |
| {"UniJIS-UTF16", |
| CIDSET_JAPAN1, |
| CIDCoding::kUTF16, |
| CPDF_CMap::TwoBytes, |
| 0, |
| {}}, |
| {"KSC-EUC", |
| CIDSET_KOREA1, |
| CIDCoding::kKOREA, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfe}}}, |
| {"KSCms-UHC", |
| CIDSET_KOREA1, |
| CIDCoding::kKOREA, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x81, 0xfe}}}, |
| {"KSCms-UHC-HW", |
| CIDSET_KOREA1, |
| CIDCoding::kKOREA, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0x81, 0xfe}}}, |
| {"KSCpc-EUC", |
| CIDSET_KOREA1, |
| CIDCoding::kKOREA, |
| CPDF_CMap::MixedTwoBytes, |
| 1, |
| {{0xa1, 0xfd}}}, |
| {"UniKS-UCS2", CIDSET_KOREA1, CIDCoding::kUCS2, CPDF_CMap::TwoBytes, 0, {}}, |
| {"UniKS-UTF16", |
| CIDSET_KOREA1, |
| CIDCoding::kUTF16, |
| CPDF_CMap::TwoBytes, |
| 0, |
| {}}, |
| }; |
| |
| const PredefinedCMap* GetPredefinedCMap(ByteStringView cmapid) { |
| if (cmapid.GetLength() > 2) |
| cmapid = cmapid.First(cmapid.GetLength() - 2); |
| for (const auto& map : kPredefinedCMaps) { |
| if (cmapid == map.m_pName) |
| return ↦ |
| } |
| return nullptr; |
| } |
| |
| std::vector<bool> LoadLeadingSegments(const PredefinedCMap& map) { |
| std::vector<bool> segments(256); |
| for (uint32_t i = 0; i < map.m_LeadingSegCount; ++i) { |
| const ByteRange& seg = map.m_LeadingSegs[i]; |
| for (int b = seg.m_First; b <= seg.m_Last; ++b) |
| segments[b] = true; |
| } |
| return segments; |
| } |
| |
| int CheckFourByteCodeRange(uint8_t* codes, |
| size_t size, |
| const std::vector<CPDF_CMap::CodeRange>& ranges) { |
| for (size_t i = ranges.size(); i > 0; i--) { |
| size_t seg = i - 1; |
| if (ranges[seg].m_CharSize < size) |
| continue; |
| size_t iChar = 0; |
| while (iChar < size) { |
| if (codes[iChar] < ranges[seg].m_Lower[iChar] || |
| codes[iChar] > ranges[seg].m_Upper[iChar]) { |
| break; |
| } |
| ++iChar; |
| } |
| if (iChar == ranges[seg].m_CharSize) |
| return 2; |
| if (iChar) |
| return (size == ranges[seg].m_CharSize) ? 2 : 1; |
| } |
| return 0; |
| } |
| |
| size_t GetFourByteCharSizeImpl( |
| uint32_t charcode, |
| const std::vector<CPDF_CMap::CodeRange>& ranges) { |
| if (ranges.empty()) |
| return 1; |
| |
| uint8_t codes[4]; |
| codes[0] = codes[1] = 0x00; |
| codes[2] = static_cast<uint8_t>(charcode >> 8 & 0xFF); |
| codes[3] = static_cast<uint8_t>(charcode); |
| for (size_t offset = 0; offset < 4; offset++) { |
| size_t size = 4 - offset; |
| for (size_t j = 0; j < ranges.size(); j++) { |
| size_t iSeg = (ranges.size() - 1) - j; |
| if (ranges[iSeg].m_CharSize < size) |
| continue; |
| size_t iChar = 0; |
| while (iChar < size) { |
| if (codes[offset + iChar] < ranges[iSeg].m_Lower[iChar] || |
| codes[offset + iChar] > ranges[iSeg].m_Upper[iChar]) { |
| break; |
| } |
| ++iChar; |
| } |
| if (iChar == ranges[iSeg].m_CharSize) |
| return size; |
| } |
| } |
| return 1; |
| } |
| |
| const FXCMAP_CMap* FindEmbeddedCMap(pdfium::span<const FXCMAP_CMap> pCMaps, |
| ByteStringView bsName) { |
| for (size_t i = 0; i < pCMaps.size(); i++) { |
| if (bsName == pCMaps[i].m_Name) |
| return &pCMaps[i]; |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| CPDF_CMap::CPDF_CMap(ByteStringView bsPredefinedName) |
| : m_bVertical(bsPredefinedName.Back() == 'V') { |
| if (bsPredefinedName == "Identity-H" || bsPredefinedName == "Identity-V") { |
| m_Coding = CIDCoding::kCID; |
| m_bLoaded = true; |
| return; |
| } |
| |
| const PredefinedCMap* map = GetPredefinedCMap(bsPredefinedName); |
| if (!map) |
| return; |
| |
| m_Charset = map->m_Charset; |
| m_Coding = map->m_Coding; |
| m_CodingScheme = map->m_CodingScheme; |
| if (m_CodingScheme == MixedTwoBytes) |
| m_MixedTwoByteLeadingBytes = LoadLeadingSegments(*map); |
| m_pEmbedMap = FindEmbeddedCMap( |
| CPDF_FontGlobals::GetInstance()->GetEmbeddedCharset(m_Charset), |
| bsPredefinedName); |
| if (!m_pEmbedMap) |
| return; |
| |
| m_bLoaded = true; |
| } |
| |
| CPDF_CMap::CPDF_CMap(pdfium::span<const uint8_t> spEmbeddedData) |
| : m_DirectCharcodeToCIDTable(kDirectMapTableSize) { |
| CPDF_CMapParser parser(this); |
| CPDF_SimpleParser syntax(spEmbeddedData); |
| while (true) { |
| ByteStringView word = syntax.GetWord(); |
| if (word.IsEmpty()) |
| break; |
| |
| parser.ParseWord(word); |
| } |
| } |
| |
| CPDF_CMap::~CPDF_CMap() = default; |
| |
| uint16_t CPDF_CMap::CIDFromCharCode(uint32_t charcode) const { |
| if (m_Coding == CIDCoding::kCID) |
| return static_cast<uint16_t>(charcode); |
| |
| if (m_pEmbedMap) |
| return ::CIDFromCharCode(m_pEmbedMap, charcode); |
| |
| if (m_DirectCharcodeToCIDTable.empty()) |
| return static_cast<uint16_t>(charcode); |
| |
| auto table_span = m_DirectCharcodeToCIDTable.span(); |
| if (charcode < table_span.size()) |
| return table_span[charcode]; |
| |
| auto it = std::lower_bound(m_AdditionalCharcodeToCIDMappings.begin(), |
| m_AdditionalCharcodeToCIDMappings.end(), charcode, |
| [](const CPDF_CMap::CIDRange& arg, uint32_t val) { |
| return arg.m_EndCode < val; |
| }); |
| if (it == m_AdditionalCharcodeToCIDMappings.end() || |
| it->m_StartCode > charcode) { |
| return 0; |
| } |
| return it->m_StartCID + charcode - it->m_StartCode; |
| } |
| |
| uint32_t CPDF_CMap::GetNextChar(ByteStringView pString, size_t* pOffset) const { |
| size_t& offset = *pOffset; |
| auto pBytes = pString.raw_span(); |
| switch (m_CodingScheme) { |
| case OneByte: { |
| return offset < pBytes.size() ? pBytes[offset++] : 0; |
| } |
| case TwoBytes: { |
| uint8_t byte1 = offset < pBytes.size() ? pBytes[offset++] : 0; |
| uint8_t byte2 = offset < pBytes.size() ? pBytes[offset++] : 0; |
| return 256 * byte1 + byte2; |
| } |
| case MixedTwoBytes: { |
| uint8_t byte1 = offset < pBytes.size() ? pBytes[offset++] : 0; |
| if (!m_MixedTwoByteLeadingBytes[byte1]) |
| return byte1; |
| uint8_t byte2 = offset < pBytes.size() ? pBytes[offset++] : 0; |
| return 256 * byte1 + byte2; |
| } |
| case MixedFourBytes: { |
| uint8_t codes[4]; |
| int char_size = 1; |
| codes[0] = offset < pBytes.size() ? pBytes[offset++] : 0; |
| while (true) { |
| int ret = CheckFourByteCodeRange(codes, char_size, |
| m_MixedFourByteLeadingRanges); |
| if (ret == 0) |
| return 0; |
| if (ret == 2) { |
| uint32_t charcode = 0; |
| for (int i = 0; i < char_size; i++) |
| charcode = (charcode << 8) + codes[i]; |
| return charcode; |
| } |
| if (char_size == 4 || offset == pBytes.size()) |
| return 0; |
| codes[char_size++] = pBytes[offset++]; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| int CPDF_CMap::GetCharSize(uint32_t charcode) const { |
| switch (m_CodingScheme) { |
| case OneByte: |
| return 1; |
| case TwoBytes: |
| return 2; |
| case MixedTwoBytes: |
| if (charcode < 0x100) |
| return 1; |
| return 2; |
| case MixedFourBytes: |
| if (charcode < 0x100) |
| return 1; |
| if (charcode < 0x10000) |
| return 2; |
| if (charcode < 0x1000000) |
| return 3; |
| return 4; |
| } |
| return 1; |
| } |
| |
| size_t CPDF_CMap::CountChar(ByteStringView pString) const { |
| switch (m_CodingScheme) { |
| case OneByte: |
| return pString.GetLength(); |
| case TwoBytes: |
| return (pString.GetLength() + 1) / 2; |
| case MixedTwoBytes: { |
| size_t count = 0; |
| for (size_t i = 0; i < pString.GetLength(); i++) { |
| count++; |
| if (m_MixedTwoByteLeadingBytes[pString[i]]) |
| i++; |
| } |
| return count; |
| } |
| case MixedFourBytes: { |
| size_t count = 0; |
| size_t offset = 0; |
| while (offset < pString.GetLength()) { |
| GetNextChar(pString, &offset); |
| count++; |
| } |
| return count; |
| } |
| } |
| return pString.GetLength(); |
| } |
| |
| int CPDF_CMap::AppendChar(char* str, uint32_t charcode) const { |
| switch (m_CodingScheme) { |
| case OneByte: |
| str[0] = static_cast<char>(charcode); |
| return 1; |
| case TwoBytes: |
| str[0] = static_cast<char>(charcode / 256); |
| str[1] = static_cast<char>(charcode % 256); |
| return 2; |
| case MixedTwoBytes: |
| if (charcode < 0x100 && !m_MixedTwoByteLeadingBytes[charcode]) { |
| str[0] = static_cast<char>(charcode); |
| return 1; |
| } |
| str[0] = static_cast<char>(charcode >> 8); |
| str[1] = static_cast<char>(charcode); |
| return 2; |
| case MixedFourBytes: |
| if (charcode < 0x100) { |
| int iSize = static_cast<int>( |
| GetFourByteCharSizeImpl(charcode, m_MixedFourByteLeadingRanges)); |
| if (iSize == 0) |
| iSize = 1; |
| str[iSize - 1] = static_cast<char>(charcode); |
| if (iSize > 1) |
| memset(str, 0, iSize - 1); |
| return iSize; |
| } |
| if (charcode < 0x10000) { |
| str[0] = static_cast<char>(charcode >> 8); |
| str[1] = static_cast<char>(charcode); |
| return 2; |
| } |
| if (charcode < 0x1000000) { |
| str[0] = static_cast<char>(charcode >> 16); |
| str[1] = static_cast<char>(charcode >> 8); |
| str[2] = static_cast<char>(charcode); |
| return 3; |
| } |
| str[0] = static_cast<char>(charcode >> 24); |
| str[1] = static_cast<char>(charcode >> 16); |
| str[2] = static_cast<char>(charcode >> 8); |
| str[3] = static_cast<char>(charcode); |
| return 4; |
| } |
| return 0; |
| } |
| |
| void CPDF_CMap::SetAdditionalMappings(std::vector<CIDRange> mappings) { |
| DCHECK(m_AdditionalCharcodeToCIDMappings.empty()); |
| if (m_CodingScheme != MixedFourBytes || mappings.empty()) |
| return; |
| |
| std::sort( |
| mappings.begin(), mappings.end(), |
| [](const CPDF_CMap::CIDRange& arg1, const CPDF_CMap::CIDRange& arg2) { |
| return arg1.m_EndCode < arg2.m_EndCode; |
| }); |
| m_AdditionalCharcodeToCIDMappings = std::move(mappings); |
| } |
| |
| void CPDF_CMap::SetMixedFourByteLeadingRanges(std::vector<CodeRange> ranges) { |
| m_MixedFourByteLeadingRanges = std::move(ranges); |
| } |