pcorless opened a new pull request, #515:
URL: https://github.com/apache/pdfbox/pull/515
# PDFBOX-6251: a CMap's own cid mappings lose to the ones it inherits via
usecmap
A CMap that starts with `usecmap` may redefine any code it inherits. It
currently can't:
`useCmap` merges the parent's `cidchar`/`cidrange` mappings into the same
collections as the
importing CMap's own, and since `usecmap` is read from the header, the
parent's land in
`codeToCid`/`codeToCidRanges` first. `toCID` scans the range list
first-match, so an inherited
range that covers a code beats the mapping the CMap declared for it.
```java
new CMapParser().parsePredefined("ETenms-B5-H").toCID(0x41, 1); // 13681,
should be 34
```
`ETenms-B5-H` exists only to do this override: it uses `ETen-B5-H`, then
remaps 0x20-0x7E to the
proportional latin CIDs 1-95 where the parent has the fullwidth forms at
13648+. Its parsed ranges
hold both, inherited first:
```
[0] 32..126 -> 13648 (from ETen-B5-H, always matches first)
[226] 32..126 -> 1 (its own, never reached)
```
**Scope:** 31 of the 92 bundled CMaps declare mappings of their own on top
of a `usecmap`, and all
31 are affected. Two are horizontal (`ETenms-B5-H`, `UniJIS-UCS2-HW-H`); the
other 29 are `-V`
variants whose own mappings select the vertical forms, so vertical CJK gets
horizontal glyphs.
**Symptom:** the wrong glyph, and usually the wrong width with it — `/W` is
indexed by CID, so a
wrong CID falls outside the array and the advance drops to `/DW` = 1000.
Latin text renders as
`J a v a S e r v e r`.
## Fix
Inherited mappings move to `inheritedCodeToCid`/`inheritedCodeToCidRanges`,
consulted only after
the CMap's own: own map -> own ranges -> inherited map -> inherited ranges.
A `usecmap` chain stays nearest-wins, which needs opposite insertion orders:
the maps are keyed, so
`putAll` is last-wins (deepest first, nearer on top); the range list is
first-match, so the nearer
CMap's ranges go in ahead.
Three things fell out of it:
- **`hasCIDMappings` has to count inherited mappings.** `Identity-V`
declares none of its own, so
once the inherited ones move it would report false and `toCID` would
return 0 for every code.
- **The maps are copied, not shared.** This kills a latent aliasing bug: the
old
`codeToCid.putIfAbsent(length, mappings)` stored the parent's inner map
*by reference* when the
importing CMap had no mapping of that length yet, so a later
`addCIDMapping` wrote into the
cached predefined parent and corrupted it for everyone after.
- **CID 0 is a mapping, not a miss.** The private range scan returned 0 for
both, which with two
range lists would send a CMap's own `cidrange` to CID 0 through to the
inherited mappings. It
now returns -1 for "no range covers this"; public `toCID` still returns 0.
## Tests
Seven added to `TestCMapParser`, `mvn -pl fontbox -am test` green (214):
| Test | Covers |
|---|---|
| `testUseCmapOwnMappingsWin` | the case above, both `toCID` overloads |
| `testUseCmapChainKeepsNearestMapping` | a two-level chain stays
nearest-wins |
| `testUseCmapOwnMappingsBeatInheritedRanges` | own cidchar and cidrange
beat an inherited range |
| `testUseCmapOnlyInheritedMappings` | `Identity-V`, everything inherited |
| `testUseCmapDoesNotShareMappingsWithTheUsedCMap` | the used CMap isn't
mutated |
| `testUseCmapOwnRangeBeatsInheritedChar` | own cidrange beats an inherited
cidchar |
| `testUseCmapOwnMappingToCidZeroIsNotAFallthrough` | CID 0 is a mapping,
not a miss |
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]