| // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <windows.h> |
| |
| #include "base/gfx/uniscribe.h" |
| |
| #include "base/gfx/font_utils.h" |
| #include "base/logging.h" |
| |
| namespace gfx { |
| |
| // This function is used to see where word spacing should be applied inside |
| // runs. Note that this must match Font::treatAsSpace so we all agree where |
| // and how much space this is, so we don't want to do more general Unicode |
| // "is this a word break" thing. |
| static bool TreatAsSpace(wchar_t c) { |
| return c == ' ' || c == '\t' || c == '\n' || c == 0x00A0; |
| } |
| |
| // SCRIPT_FONTPROPERTIES contains glyph indices for default, invalid |
| // and blank glyphs. Just because ScriptShape succeeds does not mean |
| // that a text run is rendered correctly. Some characters may be rendered |
| // with default/invalid/blank glyphs. Therefore, we need to check if the glyph |
| // array returned by ScriptShape contains any of those glyphs to make |
| // sure that the text run is rendered successfully. |
| static bool ContainsMissingGlyphs(WORD *glyphs, |
| int length, |
| SCRIPT_FONTPROPERTIES* properties) { |
| for (int i = 0; i < length; ++i) { |
| if (glyphs[i] == properties->wgDefault || |
| (glyphs[i] == properties->wgInvalid && glyphs[i] != properties->wgBlank)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // HFONT is the 'incarnation' of 'everything' about font, but it's an opaque |
| // handle and we can't directly query it to make a new HFONT sharing |
| // its characteristics (height, style, etc) except for family name. |
| // This function uses GetObject to convert HFONT back to LOGFONT, |
| // resets the fields of LOGFONT and calculates style to use later |
| // for the creation of a font identical to HFONT other than family name. |
| static void SetLogFontAndStyle(HFONT hfont, LOGFONT *logfont, int *style) { |
| DCHECK(hfont && logfont); |
| if (!hfont || !logfont) |
| return; |
| |
| GetObject(hfont, sizeof(LOGFONT), logfont); |
| // We reset these fields to values appropriate for CreateFontIndirect. |
| // while keeping lfHeight, which is the most important value in creating |
| // a new font similar to hfont. |
| logfont->lfWidth = 0; |
| logfont->lfEscapement = 0; |
| logfont->lfOrientation = 0; |
| logfont->lfCharSet = DEFAULT_CHARSET; |
| logfont->lfOutPrecision = OUT_TT_ONLY_PRECIS; |
| logfont->lfQuality = DEFAULT_QUALITY; // Honor user's desktop settings. |
| logfont->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; |
| if (style) |
| *style = gfx::GetStyleFromLogfont(logfont); |
| } |
| |
| UniscribeState::UniscribeState(const wchar_t* input, |
| int input_length, |
| bool is_rtl, |
| HFONT hfont, |
| SCRIPT_CACHE* script_cache, |
| SCRIPT_FONTPROPERTIES* font_properties) |
| : input_(input), |
| input_length_(input_length), |
| is_rtl_(is_rtl), |
| hfont_(hfont), |
| script_cache_(script_cache), |
| font_properties_(font_properties), |
| directional_override_(false), |
| inhibit_ligate_(false), |
| letter_spacing_(0), |
| space_width_(0), |
| word_spacing_(0), |
| ascent_(0) { |
| logfont_.lfFaceName[0] = 0; |
| } |
| |
| UniscribeState::~UniscribeState() { |
| } |
| |
| void UniscribeState::InitWithOptionalLengthProtection(bool length_protection) { |
| // We cap the input length and just don't do anything. We'll allocate a lot |
| // of things of the size of the number of characters, so the allocated memory |
| // will be several times the input length. Plus shaping such a large buffer |
| // may be a form of denial of service. No legitimate text should be this long. |
| // It also appears that Uniscribe flatly rejects very long strings, so we |
| // don't lose anything by doing this. |
| // |
| // The input length protection may be disabled by the unit tests to cause |
| // an error condition. |
| static const int kMaxInputLength = 65535; |
| if (input_length_ == 0 || |
| (length_protection && input_length_ > kMaxInputLength)) |
| return; |
| |
| FillRuns(); |
| FillShapes(); |
| FillScreenOrder(); |
| } |
| |
| int UniscribeState::Width() const { |
| int width = 0; |
| for (int item_index = 0; item_index < static_cast<int>(runs_->size()); |
| item_index++) { |
| width += AdvanceForItem(item_index); |
| } |
| return width; |
| } |
| |
| void UniscribeState::Justify(int additional_space) { |
| // Count the total number of glyphs we have so we know how big to make the |
| // buffers below. |
| int total_glyphs = 0; |
| for (size_t run = 0; run < runs_->size(); run++) { |
| int run_idx = screen_order_[run]; |
| total_glyphs += static_cast<int>(shapes_[run_idx].glyph_length()); |
| } |
| if (total_glyphs == 0) |
| return; // Nothing to do. |
| |
| // We make one big buffer in screen order of all the glyphs we are drawing |
| // across runs so that the justification function will adjust evenly across |
| // all glyphs. |
| StackVector<SCRIPT_VISATTR, 64> visattr; |
| visattr->resize(total_glyphs); |
| StackVector<int, 64> advances; |
| advances->resize(total_glyphs); |
| StackVector<int, 64> justify; |
| justify->resize(total_glyphs); |
| |
| // Build the packed input. |
| int dest_index = 0; |
| for (size_t run = 0; run < runs_->size(); run++) { |
| int run_idx = screen_order_[run]; |
| const Shaping& shaping = shapes_[run_idx]; |
| |
| for (int i = 0; i < shaping.glyph_length(); i++, dest_index++) { |
| memcpy(&visattr[dest_index], &shaping.visattr[i], sizeof(SCRIPT_VISATTR)); |
| advances[dest_index] = shaping.advance[i]; |
| } |
| } |
| |
| // The documentation for ScriptJustify is wrong, the parameter is the space |
| // to add and not the width of the column you want. |
| const int min_kashida = 1; // How do we decide what this should be? |
| ScriptJustify(&visattr[0], &advances[0], total_glyphs, additional_space, |
| min_kashida, &justify[0]); |
| |
| // Now we have to unpack the justification amounts back into the runs so |
| // the glyph indices match. |
| int global_glyph_index = 0; |
| for (size_t run = 0; run < runs_->size(); run++) { |
| int run_idx = screen_order_[run]; |
| Shaping& shaping = shapes_[run_idx]; |
| |
| shaping.justify->resize(shaping.glyph_length()); |
| for (int i = 0; i < shaping.glyph_length(); i++, global_glyph_index++) |
| shaping.justify[i] = justify[global_glyph_index]; |
| } |
| } |
| |
| int UniscribeState::CharacterToX(int offset) const { |
| HRESULT hr; |
| DCHECK(offset <= input_length_); |
| |
| // Our algorithm is to traverse the items in screen order from left to |
| // right, adding in each item's screen width until we find the item with |
| // the requested character in it. |
| int width = 0; |
| for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) { |
| // Compute the length of this run. |
| int item_idx = screen_order_[screen_idx]; |
| const SCRIPT_ITEM& item = runs_[item_idx]; |
| const Shaping& shaping = shapes_[item_idx]; |
| int item_length = shaping.char_length(); |
| |
| if (offset >= item.iCharPos && offset <= item.iCharPos + item_length) { |
| // Character offset is in this run. |
| int char_len = offset - item.iCharPos; |
| |
| int cur_x = 0; |
| hr = ScriptCPtoX(char_len, FALSE, item_length, shaping.glyph_length(), |
| &shaping.logs[0], &shaping.visattr[0], |
| shaping.effective_advances(), &item.a, &cur_x); |
| if (FAILED(hr)) |
| return 0; |
| |
| width += cur_x + shaping.pre_padding; |
| DCHECK(width >= 0); |
| return width; |
| } |
| |
| // Move to the next item. |
| width += AdvanceForItem(item_idx); |
| } |
| DCHECK(width >= 0); |
| return width; |
| } |
| |
| int UniscribeState::XToCharacter(int x) const { |
| // We iterate in screen order until we find the item with the given pixel |
| // position in it. When we find that guy, we ask Uniscribe for the |
| // character index. |
| HRESULT hr; |
| for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) { |
| int item_idx = screen_order_[screen_idx]; |
| int advance_for_item = AdvanceForItem(item_idx); |
| |
| // Note that the run may be empty if shaping failed, so we want to skip |
| // over it. |
| const Shaping& shaping = shapes_[item_idx]; |
| int item_length = shaping.char_length(); |
| if (x <= advance_for_item && item_length > 0) { |
| // The requested offset is within this item. |
| const SCRIPT_ITEM& item = runs_[item_idx]; |
| |
| // Account for the leading space we've added to this run that Uniscribe |
| // doesn't know about. |
| x -= shaping.pre_padding; |
| |
| int char_x = 0; |
| int trailing; |
| hr = ScriptXtoCP(x, item_length, shaping.glyph_length(), |
| &shaping.logs[0], &shaping.visattr[0], |
| shaping.effective_advances(), &item.a, &char_x, |
| &trailing); |
| |
| // The character offset is within the item. We need to add the item's |
| // offset to transform it into the space of the TextRun |
| return char_x + item.iCharPos; |
| } |
| |
| // The offset is beyond this item, account for its length and move on. |
| x -= advance_for_item; |
| } |
| |
| // Error condition, we don't know what to do if we don't have that X |
| // position in any of our items. |
| return 0; |
| } |
| |
| void UniscribeState::Draw(HDC dc, int x, int y, int from, int to) { |
| HGDIOBJ old_font = 0; |
| int cur_x = x; |
| bool first_run = true; |
| |
| for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) { |
| int item_idx = screen_order_[screen_idx]; |
| const SCRIPT_ITEM& item = runs_[item_idx]; |
| const Shaping& shaping = shapes_[item_idx]; |
| |
| // Character offsets within this run. THESE MAY NOT BE IN RANGE and may |
| // be negative, etc. The code below handles this. |
| int from_char = from - item.iCharPos; |
| int to_char = to - item.iCharPos; |
| |
| // See if we need to draw any characters in this item. |
| if (shaping.char_length() == 0 || |
| from_char >= shaping.char_length() || to_char <= 0) { |
| // No chars in this item to display. |
| cur_x += AdvanceForItem(item_idx); |
| continue; |
| } |
| |
| // Compute the starting glyph within this span. |from| and |to| are |
| // global offsets that may intersect arbitrarily with our local run. |
| int from_glyph, after_glyph; |
| if (item.a.fRTL) { |
| // To compute the first glyph when going RTL, we use |to|. |
| if (to_char >= shaping.char_length()) { |
| // The end of the text is after (to the left) of us. |
| from_glyph = 0; |
| } else { |
| // Since |to| is exclusive, the first character we draw on the left |
| // is actually the one right before (to the right) of |to|. |
| from_glyph = shaping.logs[to_char - 1]; |
| } |
| |
| // The last glyph is actually the first character in the range. |
| if (from_char <= 0) { |
| // The first character to draw is before (to the right) of this span, |
| // so draw all the way to the end. |
| after_glyph = shaping.glyph_length(); |
| } else { |
| // We want to draw everything up until the character to the right of |
| // |from|. To the right is - 1, so we look that up (remember our |
| // character could be more than one glyph, so we can't look up our |
| // glyph and add one). |
| after_glyph = shaping.logs[from_char - 1]; |
| } |
| } else { |
| // Easy case, everybody agrees about directions. We only need to handle |
| // boundary conditions to get a range inclusive at the beginning, and |
| // exclusive at the ending. We have to do some computation to see the |
| // glyph one past the end. |
| from_glyph = shaping.logs[from_char < 0 ? 0 : from_char]; |
| if (to_char >= shaping.char_length()) |
| after_glyph = shaping.glyph_length(); |
| else |
| after_glyph = shaping.logs[to_char]; |
| } |
| |
| // Account for the characters that were skipped in this run. When |
| // WebKit asks us to draw a subset of the run, it actually tells us |
| // to draw at the X offset of the beginning of the run, since it |
| // doesn't know the internal position of any of our characters. |
| const int* effective_advances = shaping.effective_advances(); |
| int inner_offset = 0; |
| for (int i = 0; i < from_glyph; i++) |
| inner_offset += effective_advances[i]; |
| |
| // Actually draw the glyphs we found. |
| int glyph_count = after_glyph - from_glyph; |
| if (from_glyph >= 0 && glyph_count > 0) { |
| // Account for the preceeding space we need to add to this run. We don't |
| // need to count for the following space because that will be counted |
| // in AdvanceForItem below when we move to the next run. |
| inner_offset += shaping.pre_padding; |
| |
| // Pass NULL in when there is no justification. |
| const int* justify = shaping.justify->empty() ? |
| NULL : &shaping.justify[from_glyph]; |
| |
| if (first_run) { |
| old_font = SelectObject(dc, shaping.hfont_); |
| first_run = false; |
| } else { |
| SelectObject(dc, shaping.hfont_); |
| } |
| |
| // TODO(brettw) bug 698452: if a half a character is selected, |
| // we should set up a clip rect so we draw the half of the glyph |
| // correctly. |
| // Fonts with different ascents can be used to render different runs. |
| // 'Across-runs' y-coordinate correction needs to be adjusted |
| // for each font. |
| HRESULT hr = S_FALSE; |
| for (int executions = 0; executions < 2; ++executions) { |
| hr = ScriptTextOut(dc, shaping.script_cache_, cur_x + inner_offset, |
| y - shaping.ascent_offset_, 0, NULL, &item.a, NULL, |
| 0, &shaping.glyphs[from_glyph], |
| glyph_count, &shaping.advance[from_glyph], |
| justify, &shaping.offsets[from_glyph]); |
| if (S_OK != hr && 0 == executions) { |
| // If this ScriptTextOut is called from the renderer it might fail |
| // because the sandbox is preventing it from opening the font files. |
| // If we are running in the renderer, TryToPreloadFont is overridden |
| // to ask the browser to preload the font for us so we can access it. |
| TryToPreloadFont(shaping.hfont_); |
| continue; |
| } |
| break; |
| } |
| |
| DCHECK(S_OK == hr); |
| |
| |
| } |
| |
| cur_x += AdvanceForItem(item_idx); |
| } |
| |
| if (old_font) |
| SelectObject(dc, old_font); |
| } |
| |
| WORD UniscribeState::FirstGlyphForCharacter(int char_offset) const { |
| // Find the run for the given character. |
| for (int i = 0; i < static_cast<int>(runs_->size()); i++) { |
| int first_char = runs_[i].iCharPos; |
| const Shaping& shaping = shapes_[i]; |
| int local_offset = char_offset - first_char; |
| if (local_offset >= 0 && local_offset < shaping.char_length()) { |
| // The character is in this run, return the first glyph for it (should |
| // generally be the only glyph). It seems Uniscribe gives glyph 0 for |
| // empty, which is what we want to return in the "missing" case. |
| size_t glyph_index = shaping.logs[local_offset]; |
| if (glyph_index >= shaping.glyphs->size()) { |
| // The glyph should be in this run, but the run has too few actual |
| // characters. This can happen when shaping the run fails, in which |
| // case, we should have no data in the logs at all. |
| DCHECK(shaping.glyphs->empty()); |
| return 0; |
| } |
| return shaping.glyphs[glyph_index]; |
| } |
| } |
| return 0; |
| } |
| |
| void UniscribeState::FillRuns() { |
| HRESULT hr; |
| runs_->resize(UNISCRIBE_STATE_STACK_RUNS); |
| |
| SCRIPT_STATE input_state; |
| input_state.uBidiLevel = is_rtl_; |
| input_state.fOverrideDirection = directional_override_; |
| input_state.fInhibitSymSwap = false; |
| input_state.fCharShape = false; // Not implemented in Uniscribe |
| input_state.fDigitSubstitute = false; // Do we want this for Arabic? |
| input_state.fInhibitLigate = inhibit_ligate_; |
| input_state.fDisplayZWG = false; // Don't draw control characters. |
| input_state.fArabicNumContext = is_rtl_; // Do we want this for Arabic? |
| input_state.fGcpClusters = false; |
| input_state.fReserved = 0; |
| input_state.fEngineReserved = 0; |
| // The psControl argument to ScriptItemize should be non-NULL for RTL text, |
| // per http://msdn.microsoft.com/en-us/library/ms776532.aspx . So use a |
| // SCRIPT_CONTROL that is set to all zeros. Zero as a locale ID means the |
| // neutral locale per http://msdn.microsoft.com/en-us/library/ms776294.aspx . |
| static SCRIPT_CONTROL input_control = {0, // uDefaultLanguage :16; |
| 0, // fContextDigits :1; |
| 0, // fInvertPreBoundDir :1; |
| 0, // fInvertPostBoundDir :1; |
| 0, // fLinkStringBefore :1; |
| 0, // fLinkStringAfter :1; |
| 0, // fNeutralOverride :1; |
| 0, // fNumericOverride :1; |
| 0, // fLegacyBidiClass :1; |
| 0, // fMergeNeutralItems :1; |
| 0};// fReserved :7; |
| // Calling ScriptApplyDigitSubstitution( NULL, &input_control, &input_state) |
| // here would be appropriate if we wanted to set the language ID, and get |
| // local digit substitution behavior. For now, don't do it. |
| |
| while (true) { |
| int num_items = 0; |
| |
| // Ideally, we would have a way to know the runs before and after this |
| // one, and put them into the control parameter of ScriptItemize. This |
| // would allow us to shape characters properly that cross style |
| // boundaries (WebKit bug 6148). |
| // |
| // We tell ScriptItemize that the output list of items is one smaller |
| // than it actually is. According to Mozilla bug 366643, if there is |
| // not enough room in the array on pre-SP2 systems, ScriptItemize will |
| // write one past the end of the buffer. |
| // |
| // ScriptItemize is very strange. It will often require a much larger |
| // ITEM buffer internally than it will give us as output. For example, |
| // it will say a 16-item buffer is not big enough, and will write |
| // interesting numbers into all those items. But when we give it a 32 |
| // item buffer and it succeeds, it only has one item output. |
| // |
| // It seems to be doing at least two passes, the first where it puts a |
| // lot of intermediate data into our items, and the second where it |
| // collates them. |
| hr = ScriptItemize(input_, input_length_, |
| static_cast<int>(runs_->size()) - 1, &input_control, &input_state, |
| &runs_[0], &num_items); |
| if (SUCCEEDED(hr)) { |
| runs_->resize(num_items); |
| break; |
| } |
| if (hr != E_OUTOFMEMORY) { |
| // Some kind of unexpected error. |
| runs_->resize(0); |
| break; |
| } |
| // There was not enough items for it to write into, expand. |
| runs_->resize(runs_->size() * 2); |
| } |
| |
| // Fix up the directions of the items so they're what WebKit thinks |
| // they are. WebKit (and we assume any other caller) always knows what |
| // direction it wants things to be in, and will only give us runs that are in |
| // the same direction. Sometimes, Uniscibe disagrees, for example, if you |
| // have embedded ASCII punctuation in an Arabic string, WebKit will |
| // (correctly) know that is should still be rendered RTL, but Uniscibe might |
| // think LTR is better. |
| // |
| // TODO(brettw) bug 747235: |
| // This workaround fixes the bug but causes spacing problems in other cases. |
| // WebKit sometimes gives us a big run that includes ASCII and Arabic, and |
| // this forcing direction makes those cases incorrect. This seems to happen |
| // during layout only, so it ends up that spacing is incorrect (because being |
| // the wrong direction changes ligatures and stuff). |
| // |
| //for (size_t i = 0; i < runs_->size(); i++) |
| // runs_[i].a.fRTL = is_rtl_; |
| } |
| |
| |
| bool UniscribeState::Shape(const wchar_t* input, |
| int item_length, |
| int num_glyphs, |
| SCRIPT_ITEM& run, |
| Shaping& shaping) { |
| HFONT hfont = hfont_; |
| SCRIPT_CACHE* script_cache = script_cache_; |
| SCRIPT_FONTPROPERTIES* font_properties = font_properties_; |
| int ascent = ascent_; |
| HDC temp_dc = NULL; |
| HGDIOBJ old_font = 0; |
| HRESULT hr; |
| bool lastFallbackTried = false; |
| bool result; |
| |
| int generated_glyphs = 0; |
| |
| // In case HFONT passed in ctor cannot render this run, we have to scan |
| // other fonts from the beginning of the font list. |
| ResetFontIndex(); |
| |
| // Compute shapes. |
| while (true) { |
| shaping.logs->resize(item_length); |
| shaping.glyphs->resize(num_glyphs); |
| shaping.visattr->resize(num_glyphs); |
| |
| // Firefox sets SCRIPT_ANALYSIS.SCRIPT_STATE.fDisplayZWG to true |
| // here. Is that what we want? It will display control characters. |
| hr = ScriptShape(temp_dc, script_cache, input, item_length, |
| num_glyphs, &run.a, |
| &shaping.glyphs[0], &shaping.logs[0], |
| &shaping.visattr[0], &generated_glyphs); |
| if (hr == E_PENDING) { |
| // Allocate the DC. |
| temp_dc = GetDC(NULL); |
| old_font = SelectObject(temp_dc, hfont); |
| continue; |
| } else if (hr == E_OUTOFMEMORY) { |
| num_glyphs *= 2; |
| continue; |
| } else if (SUCCEEDED(hr) && |
| (lastFallbackTried || !ContainsMissingGlyphs(&shaping.glyphs[0], |
| generated_glyphs, font_properties))) { |
| break; |
| } |
| |
| // The current font can't render this run. clear DC and try |
| // next font. |
| if (temp_dc) { |
| SelectObject(temp_dc, old_font); |
| ReleaseDC(NULL, temp_dc); |
| temp_dc = NULL; |
| } |
| |
| if (NextWinFontData(&hfont, &script_cache, &font_properties, &ascent)) { |
| // The primary font does not support this run. Try next font. |
| // In case of web page rendering, they come from fonts specified in |
| // CSS stylesheets. |
| continue; |
| } else if (!lastFallbackTried) { |
| lastFallbackTried = true; |
| |
| // Generate a last fallback font based on the script of |
| // a character to draw while inheriting size and styles |
| // from the primary font |
| if (!logfont_.lfFaceName[0]) |
| SetLogFontAndStyle(hfont_, &logfont_, &style_); |
| |
| // TODO(jungshik): generic type should come from webkit for |
| // UniscribeStateTextRun (a derived class used in webkit). |
| const wchar_t *family = GetFallbackFamily(input, item_length, |
| GENERIC_FAMILY_STANDARD, NULL, NULL); |
| bool font_ok = GetDerivedFontData(family, style_, &logfont_, &ascent, &hfont, &script_cache); |
| |
| if (!font_ok) { |
| // If this GetDerivedFontData is called from the renderer it might fail |
| // because the sandbox is preventing it from opening the font files. |
| // If we are running in the renderer, TryToPreloadFont is overridden to |
| // ask the browser to preload the font for us so we can access it. |
| TryToPreloadFont(hfont); |
| |
| // Try again. |
| font_ok = GetDerivedFontData(family, style_, &logfont_, &ascent, &hfont, &script_cache); |
| DCHECK(font_ok); |
| } |
| |
| // TODO(jungshik) : Currently GetDerivedHFont always returns a |
| // a valid HFONT, but in the future, I may change it to return 0. |
| DCHECK(hfont); |
| |
| // We don't need a font_properties for the last resort fallback font |
| // because we don't have anything more to try and are forced to |
| // accept empty glyph boxes. If we tried a series of fonts as |
| // 'last-resort fallback', we'd need it, but currently, we don't. |
| continue; |
| } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { |
| run.a.eScript = SCRIPT_UNDEFINED; |
| continue; |
| } else if (FAILED(hr)) { |
| // Error shaping. |
| generated_glyphs = 0; |
| result = false; |
| goto cleanup; |
| } |
| } |
| |
| // Sets Windows font data for this run to those corresponding to |
| // a font supporting this run. we don't need to store font_properties |
| // because it's not used elsewhere. |
| shaping.hfont_ = hfont; |
| shaping.script_cache_ = script_cache; |
| |
| // The ascent of a font for this run can be different from |
| // that of the primary font so that we need to keep track of |
| // the difference per run and take that into account when calling |
| // ScriptTextOut in |Draw|. Otherwise, different runs rendered by |
| // different fonts would not be aligned vertically. |
| shaping.ascent_offset_ = ascent_ ? ascent - ascent_ : 0; |
| result = true; |
| |
| cleanup: |
| shaping.glyphs->resize(generated_glyphs); |
| shaping.visattr->resize(generated_glyphs); |
| shaping.advance->resize(generated_glyphs); |
| shaping.offsets->resize(generated_glyphs); |
| if (temp_dc) { |
| SelectObject(temp_dc, old_font); |
| ReleaseDC(NULL, temp_dc); |
| } |
| // On failure, our logs don't mean anything, so zero those out. |
| if (!result) |
| shaping.logs->clear(); |
| |
| return result; |
| } |
| |
| void UniscribeState::FillShapes() { |
| shapes_->resize(runs_->size()); |
| for (size_t i = 0; i < runs_->size(); i++) { |
| int start_item = runs_[i].iCharPos; |
| int item_length = input_length_ - start_item; |
| if (i < runs_->size() - 1) |
| item_length = runs_[i + 1].iCharPos - start_item; |
| |
| int num_glyphs; |
| if (item_length < UNISCRIBE_STATE_STACK_CHARS) { |
| // We'll start our buffer sizes with the current stack space available |
| // in our buffers if the current input fits. As long as it |
| // doesn't expand past that we'll save a lot of time mallocing. |
| num_glyphs = UNISCRIBE_STATE_STACK_CHARS; |
| } else { |
| // When the input doesn't fit, give up with the stack since it will |
| // almost surely not be enough room (unless the input actually shrinks, |
| // which is unlikely) and just start with the length recommended by |
| // the Uniscribe documentation as a "usually fits" size. |
| num_glyphs = item_length * 3 / 2 + 16; |
| } |
| |
| // Convert a string to a glyph string trying the primary font, |
| // fonts in the fallback list and then script-specific last resort font. |
| Shaping& shaping = shapes_[i]; |
| if (!Shape(&input_[start_item], item_length, num_glyphs, runs_[i], shaping)) |
| continue; |
| |
| // Compute placements. Note that offsets is documented incorrectly |
| // and is actually an array. |
| |
| // DC that we lazily create if Uniscribe commands us to. |
| // (this does not happen often because script_cache is already |
| // updated when calling ScriptShape). |
| HDC temp_dc = NULL; |
| HGDIOBJ old_font = NULL; |
| HRESULT hr; |
| while (true) { |
| shaping.pre_padding = 0; |
| hr = ScriptPlace(temp_dc, shaping.script_cache_, &shaping.glyphs[0], |
| static_cast<int>(shaping.glyphs->size()), |
| &shaping.visattr[0], &runs_[i].a, |
| &shaping.advance[0], &shaping.offsets[0], |
| &shaping.abc); |
| if (hr != E_PENDING) |
| break; |
| |
| // Allocate the DC and run the loop again. |
| temp_dc = GetDC(NULL); |
| old_font = SelectObject(temp_dc, shaping.hfont_); |
| } |
| |
| if (FAILED(hr)) { |
| // Some error we don't know how to handle. Nuke all of our data |
| // since we can't deal with partially valid data later. |
| runs_->clear(); |
| shapes_->clear(); |
| screen_order_->clear(); |
| } |
| |
| if (temp_dc) { |
| SelectObject(temp_dc, old_font); |
| ReleaseDC(NULL, temp_dc); |
| } |
| } |
| |
| AdjustSpaceAdvances(); |
| |
| if (letter_spacing_ != 0 || word_spacing_ != 0) |
| ApplySpacing(); |
| } |
| |
| void UniscribeState::FillScreenOrder() { |
| screen_order_->resize(runs_->size()); |
| |
| // We assume that the input has only one text direction in it. |
| // TODO(brettw) are we sure we want to keep this restriction? |
| if (is_rtl_) { |
| for (int i = 0; i < static_cast<int>(screen_order_->size()); i++) |
| screen_order_[static_cast<int>(screen_order_->size()) - i - 1] = i; |
| } else { |
| for (int i = 0; i < static_cast<int>(screen_order_->size()); i++) |
| screen_order_[i] = i; |
| } |
| } |
| |
| void UniscribeState::AdjustSpaceAdvances() { |
| if (space_width_ == 0) |
| return; |
| |
| int space_width_without_letter_spacing = space_width_ - letter_spacing_; |
| |
| // This mostly matches what WebKit's UniscribeController::shapeAndPlaceItem. |
| for (size_t run = 0; run < runs_->size(); run++) { |
| Shaping& shaping = shapes_[run]; |
| |
| for (int i = 0; i < shaping.char_length(); i++) { |
| if (!TreatAsSpace(input_[runs_[run].iCharPos + i])) |
| continue; |
| |
| int glyph_index = shaping.logs[i]; |
| int current_advance = shaping.advance[glyph_index]; |
| // Don't give zero-width spaces a width. |
| if (!current_advance) |
| continue; |
| |
| // current_advance does not include additional letter-spacing, but |
| // space_width does. Here we find out how off we are from the correct |
| // width for the space not including letter-spacing, then just subtract |
| // that diff. |
| int diff = current_advance - space_width_without_letter_spacing; |
| // The shaping can consist of a run of text, so only subtract the |
| // difference in the width of the glyph. |
| shaping.advance[glyph_index] -= diff; |
| shaping.abc.abcB -= diff; |
| } |
| } |
| } |
| |
| void UniscribeState::ApplySpacing() { |
| for (size_t run = 0; run < runs_->size(); run++) { |
| Shaping& shaping = shapes_[run]; |
| bool is_rtl = runs_[run].a.fRTL; |
| |
| if (letter_spacing_ != 0) { |
| // RTL text gets padded to the left of each character. We increment the |
| // run's advance to make this happen. This will be balanced out by NOT |
| // adding additional advance to the last glyph in the run. |
| if (is_rtl) |
| shaping.pre_padding += letter_spacing_; |
| |
| // Go through all the glyphs in this run and increase the "advance" to |
| // account for letter spacing. We adjust letter spacing only on cluster |
| // boundaries. |
| // |
| // This works for most scripts, but may have problems with some indic |
| // scripts. This behavior is better than Firefox or IE for Hebrew. |
| for (int i = 0; i < shaping.glyph_length(); i++) { |
| if (shaping.visattr[i].fClusterStart) { |
| // Ick, we need to assign the extra space so that the glyph comes |
| // first, then is followed by the space. This is opposite for RTL. |
| if (is_rtl) { |
| if (i != shaping.glyph_length() - 1) { |
| // All but the last character just get the spacing applied to |
| // their advance. The last character doesn't get anything, |
| shaping.advance[i] += letter_spacing_; |
| shaping.abc.abcB += letter_spacing_; |
| } |
| } else { |
| // LTR case is easier, we just add to the advance. |
| shaping.advance[i] += letter_spacing_; |
| shaping.abc.abcB += letter_spacing_; |
| } |
| } |
| } |
| } |
| |
| // Go through all the characters to find whitespace and insert the extra |
| // wordspacing amount for the glyphs they correspond to. |
| if (word_spacing_ != 0) { |
| for (int i = 0; i < shaping.char_length(); i++) { |
| if (!TreatAsSpace(input_[runs_[run].iCharPos + i])) |
| continue; |
| |
| // The char in question is a word separator... |
| int glyph_index = shaping.logs[i]; |
| |
| // Spaces will not have a glyph in Uniscribe, it will just add |
| // additional advance to the character to the left of the space. The |
| // space's corresponding glyph will be the character following it in |
| // reading order. |
| if (is_rtl) { |
| // In RTL, the glyph to the left of the space is the same as the |
| // first glyph of the following character, so we can just increment |
| // it. |
| shaping.advance[glyph_index] += word_spacing_; |
| shaping.abc.abcB += word_spacing_; |
| } else { |
| // LTR is actually more complex here, we apply it to the previous |
| // character if there is one, otherwise we have to apply it to the |
| // leading space of the run. |
| if (glyph_index == 0) { |
| shaping.pre_padding += word_spacing_; |
| } else { |
| shaping.advance[glyph_index - 1] += word_spacing_; |
| shaping.abc.abcB += word_spacing_; |
| } |
| } |
| } |
| } // word_spacing_ != 0 |
| |
| // Loop for next run... |
| } |
| } |
| |
| // The advance is the ABC width of the run |
| int UniscribeState::AdvanceForItem(int item_index) const { |
| int accum = 0; |
| const Shaping& shaping = shapes_[item_index]; |
| |
| if (shaping.justify->empty()) { |
| // Easy case with no justification, the width is just the ABC width of t |
| // the run. (The ABC width is the sum of the advances). |
| return shaping.abc.abcA + shaping.abc.abcB + shaping.abc.abcC + |
| shaping.pre_padding; |
| } |
| |
| // With justification, we use the justified amounts instead. The |
| // justification array contains both the advance and the extra space |
| // added for justification, so is the width we want. |
| int justification = 0; |
| for (size_t i = 0; i < shaping.justify->size(); i++) |
| justification += shaping.justify[i]; |
| |
| return shaping.pre_padding + justification; |
| } |
| |
| } // namespace gfx |
| |