1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/pdf/SkPDFMakeCIDGlyphWidthsArray.h"
9 
10 #include "include/core/SkPaint.h"
11 #include "include/private/SkTo.h"
12 #include "src/core/SkStrike.h"
13 #include "src/core/SkStrikeSpec.h"
14 #include "src/pdf/SkPDFGlyphUse.h"
15 
16 #include <algorithm>
17 #include <vector>
18 
19 // TODO(halcanary): Write unit tests for SkPDFMakeCIDGlyphWidthsArray().
20 
21 // TODO(halcanary): The logic in this file originated in several
22 // disparate places.  I feel sure that someone could simplify this
23 // down to a single easy-to-read function.
24 
25 namespace {
26 
27 // scale from em-units to base-1000, returning as a SkScalar
from_font_units(SkScalar scaled,uint16_t emSize)28 SkScalar from_font_units(SkScalar scaled, uint16_t emSize) {
29     if (emSize == 1000) {
30         return scaled;
31     } else {
32         return scaled * 1000 / emSize;
33     }
34 }
35 
scale_from_font_units(int16_t val,uint16_t emSize)36 SkScalar scale_from_font_units(int16_t val, uint16_t emSize) {
37     return from_font_units(SkIntToScalar(val), emSize);
38 }
39 
40 // Unfortunately poppler does not appear to respect the default width setting.
41 #if defined(SK_PDF_CAN_USE_DW)
findMode(SkSpan<const int16_t> advances)42 int16_t findMode(SkSpan<const int16_t> advances) {
43     if (advances.empty()) {
44         return 0;
45     }
46 
47     int16_t previousAdvance = advances[0];
48     int16_t currentModeAdvance = advances[0];
49     size_t currentCount = 1;
50     size_t currentModeCount = 1;
51 
52     for (size_t i = 1; i < advances.size(); ++i) {
53         if (advances[i] == previousAdvance) {
54             ++currentCount;
55         } else {
56             if (currentCount > currentModeCount) {
57                 currentModeAdvance = previousAdvance;
58                 currentModeCount = currentCount;
59             }
60             previousAdvance = advances[i];
61             currentCount = 1;
62         }
63     }
64 
65     return currentCount > currentModeCount ? previousAdvance : currentModeAdvance;
66 }
67 #endif
68 } // namespace
69 
70 /** Retrieve advance data for glyphs. Used by the PDF backend. */
71 // TODO(halcanary): this function is complex enough to need its logic
72 // tested with unit tests.
SkPDFMakeCIDGlyphWidthsArray(const SkTypeface & typeface,const SkPDFGlyphUse & subset,SkScalar * defaultAdvance)73 std::unique_ptr<SkPDFArray> SkPDFMakeCIDGlyphWidthsArray(const SkTypeface& typeface,
74                                                          const SkPDFGlyphUse& subset,
75                                                          SkScalar* defaultAdvance) {
76     // There are two ways of expressing advances
77     //
78     // range: " gfid [adv.ances adv.ances ... adv.ances]"
79     //   run: " gfid gfid adv.ances"
80     //
81     // Assuming that on average
82     // the ASCII representation of an advance plus a space is 10 characters
83     // the ASCII representation of a glyph id plus a space is 4 characters
84     // the ASCII representation of unused gid plus a space in a range is 2 characters
85     //
86     // When not in a range or run
87     //  a. Skipping don't cares or defaults is a win (trivial)
88     //  b. Run wins for 2+ repeats " gid gid adv.ances"
89     //                             " gid [adv.ances adv.ances]"
90     //     rule: 2+ repeats create run as long as possible, else start range
91     //
92     // When in a range
93     // Cost of stopping and starting a range is 8 characters  "] gid ["
94     //  c. Skipping defaults is always a win                  " adv.ances"
95     //     rule: end range if default seen
96     //  d. Skipping 4+ don't cares is a win                   " 0 0 0 0"
97     //     rule: end range if 4+ don't cares
98     // Cost of stop and start range plus run is 28 characters "] gid gid adv.ances gid ["
99     //  e. Switching for 2+ repeats and 4+ don't cares wins   " 0 0 adv.ances 0 0 adv.ances"
100     //     rule: end range for 2+ repeats with 4+ don't cares
101     //  f. Switching for 3+ repeats wins                      " adv.ances adv.ances adv.ances"
102     //     rule: end range for 3+ repeats
103 
104     int emSize;
105     SkStrikeSpec strikeSpec = SkStrikeSpec::MakePDFVector(typeface, &emSize);
106     SkBulkGlyphMetricsAndPaths paths{strikeSpec};
107 
108     auto result = SkPDFMakeArray();
109 
110     std::vector<SkGlyphID> glyphIDs;
111     subset.getSetValues([&](unsigned index) {
112         glyphIDs.push_back(SkToU16(index));
113     });
114     auto glyphs = paths.glyphs(SkMakeSpan(glyphIDs));
115 
116 #if defined(SK_PDF_CAN_USE_DW)
117     std::vector<int16_t> advances;
118     advances.reserve(glyphs.size());
119     for (const SkGlyph* glyph : glyphs) {
120         advances.push_back((int16_t)glyph->advanceX());
121     }
122     std::sort(advances.begin(), advances.end());
123     int16_t modeAdvance = findMode(SkMakeSpan(advances));
124     *defaultAdvance = scale_from_font_units(modeAdvance, emSize);
125 #else
126     *defaultAdvance = 0;
127 #endif
128 
129     for (size_t i = 0; i < glyphs.size(); ++i) {
130         int16_t advance = (int16_t)glyphs[i]->advanceX();
131 
132 #if defined(SK_PDF_CAN_USE_DW)
133         // a. Skipping don't cares or defaults is a win (trivial)
134         if (advance == modeAdvance) {
135             continue;
136         }
137 #endif
138 
139         // b. 2+ repeats create run as long as possible, else start range
140         {
141             size_t j = i + 1; // j is always one past the last known repeat
142             for (; j < glyphs.size(); ++j) {
143                 int16_t next_advance = (int16_t)glyphs[j]->advanceX();
144                 if (advance != next_advance) {
145                     break;
146                 }
147             }
148             if (j - i >= 2) {
149                 result->appendInt(glyphs[i]->getGlyphID());
150                 result->appendInt(glyphs[j - 1]->getGlyphID());
151                 result->appendScalar(scale_from_font_units(advance, emSize));
152                 i = j - 1;
153                 continue;
154             }
155         }
156 
157         {
158             result->appendInt(glyphs[i]->getGlyphID());
159             auto advanceArray = SkPDFMakeArray();
160             advanceArray->appendScalar(scale_from_font_units(advance, emSize));
161             size_t j = i + 1; // j is always one past the last output
162             for (; j < glyphs.size(); ++j) {
163                 advance = (int16_t)glyphs[j]->advanceX();
164 #if defined(SK_PDF_CAN_USE_DW)
165                 // c. end range if default seen
166                 if (advance == modeAdvance) {
167                     break;
168                 }
169 #endif
170 
171                 int dontCares = glyphs[j]->getGlyphID() - glyphs[j - 1]->getGlyphID() - 1;
172                 // d. end range if 4+ don't cares
173                 if (dontCares >= 4) {
174                     break;
175                 }
176 
177                 int16_t next_advance = 0;
178                 // e. end range for 2+ repeats with 4+ don't cares
179                 if (j + 1 < glyphs.size()) {
180                     next_advance = (int16_t)glyphs[j+1]->advanceX();
181                     int next_dontCares = glyphs[j+1]->getGlyphID() - glyphs[j]->getGlyphID() - 1;
182                     if (advance == next_advance && dontCares + next_dontCares >= 4) {
183                         break;
184                     }
185                 }
186 
187                 // f. end range for 3+ repeats
188                 if (j + 2 < glyphs.size() && advance == next_advance) {
189                     next_advance = (int16_t)glyphs[j+2]->advanceX();
190                     if (advance == next_advance) {
191                         break;
192                     }
193                 }
194 
195                 while (dontCares --> 0) {
196                     advanceArray->appendScalar(0);
197                 }
198                 advanceArray->appendScalar(scale_from_font_units(advance, emSize));
199             }
200             result->appendObject(std::move(advanceArray));
201             i = j - 1;
202         }
203     }
204 
205     return result;
206 }
207